diff --git a/docs/diff-tool.custom.md b/docs/diff-tool.custom.md index a3c2453a..b503b346 100644 --- a/docs/diff-tool.custom.md +++ b/docs/diff-tool.custom.md @@ -22,7 +22,7 @@ var resolvedTool = DiffTools.AddTool( exePath: diffToolPath, binaryExtensions: new[] {"jpg"})!; ``` -snippet source | anchor +snippet source | anchor Add a tool based on existing resolved tool: @@ -35,7 +35,7 @@ var resolvedTool = DiffTools.AddToolBasedOn( name: "MyCustomDiffTool", arguments: (temp, target) => $"\"custom args {temp}\" \"{target}\""); ``` -snippet source | anchor +snippet source | anchor @@ -63,7 +63,7 @@ var resolvedTool = DiffTools.AddToolBasedOn( await DiffRunner.LaunchAsync(resolvedTool!, "PathToTempFile", "PathToTargetFile"); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/diff-tool.md b/docs/diff-tool.md index 32e1ce70..824a9787 100644 --- a/docs/diff-tool.md +++ b/docs/diff-tool.md @@ -61,7 +61,7 @@ Setting the `DiffEngine_MaxInstances` environment variable to the number of inst ```cs DiffRunner.MaxInstancesToLaunch(10); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/diff-tool.order.md b/docs/diff-tool.order.md index 85af01ff..b213d965 100644 --- a/docs/diff-tool.order.md +++ b/docs/diff-tool.order.md @@ -52,5 +52,5 @@ For example `VisualStudio,Meld` will result in VisualStudio then Meld then all o ```cs DiffTools.UseOrder(DiffTool.VisualStudio, DiffTool.AraxisMerge); ``` -snippet source | anchor +snippet source | anchor diff --git a/src/DiffEngine.Tests/DiffToolsTest.cs b/src/DiffEngine.Tests/DiffToolsTest.cs index b7585797..5a59b31b 100644 --- a/src/DiffEngine.Tests/DiffToolsTest.cs +++ b/src/DiffEngine.Tests/DiffToolsTest.cs @@ -1,6 +1,8 @@ using System.Linq; +using System.Runtime.InteropServices; using System.Threading.Tasks; using DiffEngine; +using DiffEngine.Tests; using Xunit; using Xunit.Abstractions; @@ -37,6 +39,28 @@ public void AddTool() Assert.Equal(resolvedTool.Name, forExtension!.Name); } + [Fact] + public void MacDiffOrderShouldNotMessWithAddTool() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var diffToolPath = MacDiffTool.Exe; + var resolvedTool = DiffTools.AddTool( + name: "MacDiffTool", + autoRefresh: true, + isMdi: false, + supportsText: true, + requiresTarget: true, + arguments: (tempFile, targetFile) => $"\"{tempFile}\" \"{targetFile}\"", + exePath: diffToolPath, + binaryExtensions: Enumerable.Empty())!; + DiffTools.UseOrder(DiffTool.VisualStudio, DiffTool.AraxisMerge); + Assert.Equal(resolvedTool.Name, DiffTools.Resolved.First().Name); + Assert.True(DiffTools.TryFind("txt", out var forExtension)); + Assert.Equal(resolvedTool.Name, forExtension!.Name); + } + } + [Fact] public void OrderShouldNotMessWithAddTool() { diff --git a/src/DiffEngine.Tests/MacDiffTool.cs b/src/DiffEngine.Tests/MacDiffTool.cs new file mode 100644 index 00000000..9838873c --- /dev/null +++ b/src/DiffEngine.Tests/MacDiffTool.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace DiffEngine.Tests +{ + public class MacDiffTool + { + public static string Exe; + + static MacDiffTool() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Exe = Path.GetFullPath(Path.Combine(AssemblyLocation.CurrentDirectory, "../../../../DiffEngineTray.Mac/bin/Debug/DiffEngineTray.Mac.app")); + return; + } + + throw new Exception(); + } + } +} \ No newline at end of file diff --git a/src/DiffEngine.sln b/src/DiffEngine.sln index eb749ffe..6ec0648f 100644 --- a/src/DiffEngine.sln +++ b/src/DiffEngine.sln @@ -22,6 +22,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiffEngine.Tests", "DiffEng EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiffEngineTray.Tests", "DiffEngineTray.Tests\DiffEngineTray.Tests.csproj", "{E339AB28-21B2-47DB-B995-EB2C4CE8BA99}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffEngineTray.Common", "DiffEngineTray.Common\DiffEngineTray.Common.csproj", "{B62FBE96-EB1E-4621-AA31-8971862FC4FB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,6 +59,12 @@ Global {E339AB28-21B2-47DB-B995-EB2C4CE8BA99}.MacOS|Any CPU.ActiveCfg = MacOS|Any CPU {E339AB28-21B2-47DB-B995-EB2C4CE8BA99}.Release|Any CPU.ActiveCfg = Release|Any CPU {E339AB28-21B2-47DB-B995-EB2C4CE8BA99}.Release|Any CPU.Build.0 = Release|Any CPU + {B62FBE96-EB1E-4621-AA31-8971862FC4FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B62FBE96-EB1E-4621-AA31-8971862FC4FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B62FBE96-EB1E-4621-AA31-8971862FC4FB}.MacOS|Any CPU.ActiveCfg = Debug|Any CPU + {B62FBE96-EB1E-4621-AA31-8971862FC4FB}.MacOS|Any CPU.Build.0 = Debug|Any CPU + {B62FBE96-EB1E-4621-AA31-8971862FC4FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B62FBE96-EB1E-4621-AA31-8971862FC4FB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/DiffEngine/DiffEngine.csproj b/src/DiffEngine/DiffEngine.csproj index d18bec0a..5cc10660 100644 --- a/src/DiffEngine/DiffEngine.csproj +++ b/src/DiffEngine/DiffEngine.csproj @@ -6,8 +6,8 @@ netstandard2.0;netstandard2.1;net461 - - + + diff --git a/src/DiffEngineTray/AssemblyLocation.cs b/src/DiffEngineTray.Common/AssemblyLocation.cs similarity index 100% rename from src/DiffEngineTray/AssemblyLocation.cs rename to src/DiffEngineTray.Common/AssemblyLocation.cs diff --git a/src/DiffEngineTray/AsyncTimer.cs b/src/DiffEngineTray.Common/AsyncTimer.cs similarity index 100% rename from src/DiffEngineTray/AsyncTimer.cs rename to src/DiffEngineTray.Common/AsyncTimer.cs diff --git a/src/DiffEngineTray.Common/DiffEngineTray.Common.csproj b/src/DiffEngineTray.Common/DiffEngineTray.Common.csproj new file mode 100644 index 00000000..c7b7504d --- /dev/null +++ b/src/DiffEngineTray.Common/DiffEngineTray.Common.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.1 + 8 + false + A utility that runs in the windows tray and handles the results of file differences. + + + + + + + + + + + + diff --git a/src/DiffEngineTray/DirectoryLauncher.cs b/src/DiffEngineTray.Common/DirectoryLauncher.cs similarity index 100% rename from src/DiffEngineTray/DirectoryLauncher.cs rename to src/DiffEngineTray.Common/DirectoryLauncher.cs diff --git a/src/DiffEngineTray/ExceptionHandler.cs b/src/DiffEngineTray.Common/ExceptionHandler.cs similarity index 100% rename from src/DiffEngineTray/ExceptionHandler.cs rename to src/DiffEngineTray.Common/ExceptionHandler.cs diff --git a/src/DiffEngineTray/FileComparer.cs b/src/DiffEngineTray.Common/FileComparer.cs similarity index 95% rename from src/DiffEngineTray/FileComparer.cs rename to src/DiffEngineTray.Common/FileComparer.cs index 23870c88..47fc76c1 100644 --- a/src/DiffEngineTray/FileComparer.cs +++ b/src/DiffEngineTray.Common/FileComparer.cs @@ -28,8 +28,8 @@ public static async Task FilesAreEqual(string file1, string file2) return false; } - await using var fs1 = OpenRead(file1); - await using var fs2 = OpenRead(file2); + using var fs1 = OpenRead(file1); + using var fs2 = OpenRead(file2); return await StreamsAreEqual(fs1, fs2); } diff --git a/src/DiffEngineTray.Common/IMessageBox.cs b/src/DiffEngineTray.Common/IMessageBox.cs new file mode 100644 index 00000000..72e81cb1 --- /dev/null +++ b/src/DiffEngineTray.Common/IMessageBox.cs @@ -0,0 +1,25 @@ +namespace DiffEngineTray.Common +{ + public enum MessageBoxIcon + { + Error + } + + public enum MessageBoxButtons + { + YesNo, + OK + } + + public enum AlertResult + { + NSAlertFirstButtonReturn = 1000, + NSAlertSecondButtonReturn = 1001, + NSAlertThirdButtonReturn = 1002 + } + + public interface IMessageBox + { + public bool? Show(string message, string title, MessageBoxIcon icon, MessageBoxButtons buttons = MessageBoxButtons.YesNo); + } +} \ No newline at end of file diff --git a/src/DiffEngineTray.Common/IUpdater.cs b/src/DiffEngineTray.Common/IUpdater.cs new file mode 100644 index 00000000..36496adb --- /dev/null +++ b/src/DiffEngineTray.Common/IUpdater.cs @@ -0,0 +1,7 @@ +namespace DiffEngineTray.Common +{ + public interface IUpdater + { + void Run(); + } +} \ No newline at end of file diff --git a/src/DiffEngineTray.Common/InternalsVisibleTo.cs b/src/DiffEngineTray.Common/InternalsVisibleTo.cs new file mode 100644 index 00000000..5426592f --- /dev/null +++ b/src/DiffEngineTray.Common/InternalsVisibleTo.cs @@ -0,0 +1,7 @@ +using System.Runtime.CompilerServices; + +[assembly:InternalsVisibleTo("DiffEngineTray.Tests")] +[assembly:InternalsVisibleTo("DiffEngineTray")] +[assembly:InternalsVisibleTo("DiffEngineTray.Mac")] +[assembly:InternalsVisibleTo("DiffEngineTray.Mac.Tests")] +[assembly:InternalsVisibleTo("DiffEngine")] diff --git a/src/DiffEngineTray/IssueLauncher.cs b/src/DiffEngineTray.Common/IssueLauncher.cs similarity index 83% rename from src/DiffEngineTray/IssueLauncher.cs rename to src/DiffEngineTray.Common/IssueLauncher.cs index bf47df81..c9b49c28 100644 --- a/src/DiffEngineTray/IssueLauncher.cs +++ b/src/DiffEngineTray.Common/IssueLauncher.cs @@ -2,19 +2,25 @@ using System.Collections.Concurrent; using System.Linq; using System.Net; -using System.Windows.Forms; +using DiffEngineTray.Common; -static class IssueLauncher +class IssueLauncher { static ConcurrentBag recorded = new ConcurrentBag(); static string defaultBody; - + static IMessageBox? messageBox; + static IssueLauncher() { defaultBody = WebUtility.UrlEncode($@" * DiffEngineTray Version: {VersionReader.VersionString} * OS: {Environment.OSVersion.VersionString}"); } + public static void Initialize(IMessageBox message) + { + messageBox = message; + } + public static void Launch() { LinkLauncher.LaunchUrl($"https://github.com/VerifyTests/DiffEngine/issues/new?title=TODO&body={defaultBody}"); @@ -28,7 +34,7 @@ public static void LaunchForException(string message, Exception exception) } recorded.Add(message); - var result = MessageBox.Show( + var result = messageBox?.Show( $@"An error occurred: {message} Logged to: {Logging.Directory} @@ -37,9 +43,8 @@ public static void LaunchForException(string message, Exception exception) Open an issue on GitHub?", "DiffEngineTray Error", - MessageBoxButtons.YesNo, MessageBoxIcon.Error); - if (result == DialogResult.No) + if (result != true) { return; } diff --git a/src/DiffEngineTray/JsonEscaping.cs b/src/DiffEngineTray.Common/JsonEscaping.cs similarity index 100% rename from src/DiffEngineTray/JsonEscaping.cs rename to src/DiffEngineTray.Common/JsonEscaping.cs diff --git a/src/DiffEngineTray/LinkLauncher.cs b/src/DiffEngineTray.Common/LinkLauncher.cs similarity index 100% rename from src/DiffEngineTray/LinkLauncher.cs rename to src/DiffEngineTray.Common/LinkLauncher.cs diff --git a/src/DiffEngineTray/Logging.cs b/src/DiffEngineTray.Common/Logging.cs similarity index 100% rename from src/DiffEngineTray/Logging.cs rename to src/DiffEngineTray.Common/Logging.cs diff --git a/src/DiffEngineTray/Payloads/DeletePayload.cs b/src/DiffEngineTray.Common/Payloads/DeletePayload.cs similarity index 100% rename from src/DiffEngineTray/Payloads/DeletePayload.cs rename to src/DiffEngineTray.Common/Payloads/DeletePayload.cs diff --git a/src/DiffEngineTray/Payloads/MovePayload.cs b/src/DiffEngineTray.Common/Payloads/MovePayload.cs similarity index 100% rename from src/DiffEngineTray/Payloads/MovePayload.cs rename to src/DiffEngineTray.Common/Payloads/MovePayload.cs diff --git a/src/DiffEngineTray/PiperClient.cs b/src/DiffEngineTray.Common/PiperClient.cs similarity index 100% rename from src/DiffEngineTray/PiperClient.cs rename to src/DiffEngineTray.Common/PiperClient.cs diff --git a/src/DiffEngineTray/PiperServer.cs b/src/DiffEngineTray.Common/PiperServer.cs similarity index 100% rename from src/DiffEngineTray/PiperServer.cs rename to src/DiffEngineTray.Common/PiperServer.cs diff --git a/src/DiffEngineTray/ProcessEx.cs b/src/DiffEngineTray.Common/ProcessEx.cs similarity index 100% rename from src/DiffEngineTray/ProcessEx.cs rename to src/DiffEngineTray.Common/ProcessEx.cs diff --git a/src/DiffEngineTray/Serializer.cs b/src/DiffEngineTray.Common/Serializer.cs similarity index 100% rename from src/DiffEngineTray/Serializer.cs rename to src/DiffEngineTray.Common/Serializer.cs diff --git a/src/DiffEngineTray/SolutionDirectoryFinder.cs b/src/DiffEngineTray.Common/SolutionDirectoryFinder.cs similarity index 100% rename from src/DiffEngineTray/SolutionDirectoryFinder.cs rename to src/DiffEngineTray.Common/SolutionDirectoryFinder.cs diff --git a/src/DiffEngineTray/TrackedDelete.cs b/src/DiffEngineTray.Common/TrackedDelete.cs similarity index 100% rename from src/DiffEngineTray/TrackedDelete.cs rename to src/DiffEngineTray.Common/TrackedDelete.cs diff --git a/src/DiffEngineTray/TrackedMove.cs b/src/DiffEngineTray.Common/TrackedMove.cs similarity index 100% rename from src/DiffEngineTray/TrackedMove.cs rename to src/DiffEngineTray.Common/TrackedMove.cs diff --git a/src/DiffEngineTray/Tracker.cs b/src/DiffEngineTray.Common/Tracker.cs similarity index 98% rename from src/DiffEngineTray/Tracker.cs rename to src/DiffEngineTray.Common/Tracker.cs index 0912d198..f135eb9d 100644 --- a/src/DiffEngineTray/Tracker.cs +++ b/src/DiffEngineTray.Common/Tracker.cs @@ -189,7 +189,7 @@ static void InnerMove(TrackedMove move) { if (File.Exists(move.Temp)) { - File.Move(move.Temp, move.Target, true); + File.Move(move.Temp, move.Target); //Overload not available here } KillProcesses(move); diff --git a/src/DiffEngineTray/VersionReader.cs b/src/DiffEngineTray.Common/VersionReader.cs similarity index 100% rename from src/DiffEngineTray/VersionReader.cs rename to src/DiffEngineTray.Common/VersionReader.cs diff --git a/src/DiffEngineTray.Mac.Tests/AsyncTimerTests.cs b/src/DiffEngineTray.Mac.Tests/AsyncTimerTests.cs new file mode 100644 index 00000000..bc6da30a --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/AsyncTimerTests.cs @@ -0,0 +1,122 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +public class AsyncTimerTests +{ + [Fact] + public async Task It_calls_error_callback() + { + var errorCallbackInvoked = new TaskCompletionSource(); + + var timer = new AsyncTimer( + callback: (time, token) => throw new Exception("Simulated!"), + interval: TimeSpan.Zero, + errorCallback: e => { errorCallbackInvoked.SetResult(true); }); + + Assert.True(await errorCallbackInvoked.Task); + } + + [Fact] + public async Task It_continues_to_run_after_an_error() + { + var callbackInvokedAfterError = new TaskCompletionSource(); + + var fail = true; + var exceptionThrown = false; + var timer = new AsyncTimer( + callback: (time, token) => + { + if (fail) + { + fail = false; + throw new Exception("Simulated!"); + } + + Assert.True(exceptionThrown); + callbackInvokedAfterError.SetResult(true); + return Task.FromResult(0); + }, + interval: TimeSpan.Zero, + errorCallback: e => { exceptionThrown = true; }); + + Assert.True(await callbackInvokedAfterError.Task); + } + + [Fact] + public async Task Stop_cancels_token_while_waiting() + { + var waitCanceled = false; + var delayStarted = new TaskCompletionSource(); + var timer = new AsyncTimer( + callback: (time, token) => throw new Exception("Simulated!"), + interval: TimeSpan.FromDays(7), + delayStrategy: async (delayTime, token) => + { + delayStarted.SetResult(true); + try + { + await Task.Delay(delayTime, token); + } + catch (OperationCanceledException) + { + waitCanceled = true; + } + }); + + await delayStarted.Task; + await timer.DisposeAsync(); + + Assert.True(waitCanceled); + } + + [Fact] + public async Task Stop_cancels_token_while_in_callback() + { + var callbackCanceled = false; + var callbackStarted = new TaskCompletionSource(); + var stopInitiated = new TaskCompletionSource(); + var timer = new AsyncTimer( + callback: async (time, token) => + { + callbackStarted.SetResult(true); + await stopInitiated.Task; + if (token.IsCancellationRequested) + { + callbackCanceled = true; + } + }, + interval: TimeSpan.Zero); + + await callbackStarted.Task; + var stopTask = timer.DisposeAsync(); + stopInitiated.SetResult(true); + await stopTask; + Assert.True(callbackCanceled); + } + + [Fact] + public async Task Stop_waits_for_callback_to_complete() + { + var callbackCompleted = new TaskCompletionSource(); + var callbackTaskStarted = new TaskCompletionSource(); + var timer = new AsyncTimer( + callback: (time, token) => + { + callbackTaskStarted.SetResult(true); + return callbackCompleted.Task; + }, + interval: TimeSpan.Zero); + + await callbackTaskStarted.Task; + + var stopTask = timer.DisposeAsync().AsTask(); + var delayTask = Task.Delay(1000); + + var firstToComplete = await Task.WhenAny(stopTask, delayTask); + Assert.Equal(delayTask, firstToComplete); + callbackCompleted.SetResult(true); + + await stopTask; + } +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/DiffEngineTray.Mac.Tests.csproj b/src/DiffEngineTray.Mac.Tests/DiffEngineTray.Mac.Tests.csproj new file mode 100644 index 00000000..08734ae5 --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/DiffEngineTray.Mac.Tests.csproj @@ -0,0 +1,42 @@ + + + + netcoreapp3.1 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/FodyWeavers.xml b/src/DiffEngineTray.Mac.Tests/FodyWeavers.xml new file mode 100644 index 00000000..e107bdbc --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.Default.verified.png b/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.Default.verified.png new file mode 100644 index 00000000..ee8d4bd8 Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.Default.verified.png differ diff --git a/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.WithKeys.verified.png b/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.WithKeys.verified.png new file mode 100644 index 00000000..742ed610 Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.WithKeys.verified.png differ diff --git a/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.cs b/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.cs new file mode 100644 index 00000000..1a145d19 --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using VerifyXunit; +using Xunit; +using Xunit.Abstractions; + +[UsesVerify] +public class HotKeyControlTests : + XunitContextBase +{ + public HotKeyControlTests(ITestOutputHelper output) : + base(output) + { + } + +#if DEBUG + [Fact] + public async Task WithKeys() + { + using var target = new HotKeyControl + { + HotKey = new HotKey + { + Shift = true, + Key = "A" + } + }; + await Verifier.Verify(target); + } + + [Fact] + public async Task Default() + { + using var target = new HotKeyControl(); + await Verifier.Verify(target); + } +#endif +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.Empty.verified.png b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.Empty.verified.png new file mode 100644 index 00000000..46f61742 Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.Empty.verified.png differ diff --git a/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.Full.verified.png b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.Full.verified.png new file mode 100644 index 00000000..0f02ef4b Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.Full.verified.png differ diff --git a/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.OnlyDelete.verified.png b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.OnlyDelete.verified.png new file mode 100644 index 00000000..aaed2d16 Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.OnlyDelete.verified.png differ diff --git a/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.OnlyMove.verified.png b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.OnlyMove.verified.png new file mode 100644 index 00000000..e0caa2f6 Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.OnlyMove.verified.png differ diff --git a/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.cs b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.cs new file mode 100644 index 00000000..bb7c62b8 --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.cs @@ -0,0 +1,78 @@ +using System.IO; +using System.Threading.Tasks; +using VerifyTests; +using VerifyXunit; +using Xunit; +using Xunit.Abstractions; + +[UsesVerify] +public class MenuBuilderTest : + XunitContextBase +{ + [Fact] + public async Task Empty() + { + await using var tracker = new RecordingTracker(); + var menu = MenuBuilder.Build(() => { }, () => { }, tracker); + await Verifier.Verify(menu, settings); + } + + [Fact] + public async Task OnlyMove() + { + await using var tracker = new RecordingTracker(); + tracker.AddMove(file2, file2, "theExe", "theArguments", true, null); + var menu = MenuBuilder.Build(() => { }, () => { }, tracker); + await Verifier.Verify(menu, settings); + } + + [Fact] + public async Task OnlyDelete() + { + await using var tracker = new RecordingTracker(); + tracker.AddDelete(file1); + var menu = MenuBuilder.Build(() => { }, () => { }, tracker); + await Verifier.Verify(menu, settings); + } + + [Fact] + public async Task Full() + { + await using var tracker = new RecordingTracker(); + tracker.AddDelete(file1); + tracker.AddDelete(file2); + tracker.AddMove(file3, file3, "theExe", "theArguments", true, null); + tracker.AddMove(file4, file4, "theExe", "theArguments", true, null); + var menu = MenuBuilder.Build(() => { }, () => { }, tracker); + await Verifier.Verify(menu, settings); + } + + public MenuBuilderTest(ITestOutputHelper output) : + base(output) + { + settings = new VerifySettings(); + file1 = "file1.txt"; + file2 = "file2.txt"; + file3 = "file3.txt"; + file4 = "file4.txt"; + File.WriteAllText(file1, ""); + File.WriteAllText(file2, ""); + File.WriteAllText(file3, ""); + File.WriteAllText(file4, ""); + } + + public override void Dispose() + { + File.Delete(file1); + File.Delete(file2); + File.Delete(file3); + File.Delete(file4); + base.Dispose(); + } + + string file1; + string file2; + string file3; + string file4; + VerifySettings settings; +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/ModuleInitializer.cs b/src/DiffEngineTray.Mac.Tests/ModuleInitializer.cs new file mode 100644 index 00000000..63701f01 --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/ModuleInitializer.cs @@ -0,0 +1,11 @@ +using VerifyTests; + +public static class ModuleInitializer +{ + public static void Initialize() + { + VerifierSettings.DisableNewLineEscaping(); + VerifyImageMagick.Initialize(); + VerifyImageMagick.RegisterComparers(); + } +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/OptionsFormTests.Default.verified.png b/src/DiffEngineTray.Mac.Tests/OptionsFormTests.Default.verified.png new file mode 100644 index 00000000..1928a666 Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/OptionsFormTests.Default.verified.png differ diff --git a/src/DiffEngineTray.Mac.Tests/OptionsFormTests.WithKeys.verified.png b/src/DiffEngineTray.Mac.Tests/OptionsFormTests.WithKeys.verified.png new file mode 100644 index 00000000..68448324 Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/OptionsFormTests.WithKeys.verified.png differ diff --git a/src/DiffEngineTray.Mac.Tests/OptionsFormTests.cs b/src/DiffEngineTray.Mac.Tests/OptionsFormTests.cs new file mode 100644 index 00000000..c648d019 --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/OptionsFormTests.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using VerifyXunit; +using Xunit; +using Xunit.Abstractions; + +[UsesVerify] +public class OptionsFormTests : + XunitContextBase +{ + public OptionsFormTests(ITestOutputHelper output) : + base(output) + { + VersionReader.VersionString = "TheVersion"; + } + + //[Fact] + //[Trait("Category", "Integration")] + //public void Launch() + //{ + // using var form = new OptionsForm( + // new Settings + // { + // AcceptAllHotKey = new HotKey + // { + // Shift = true, + // Key = "A" + // } + // }, + // x => Task.FromResult>(new List())); + // form.ShowDialog(); + // form.BringToFront(); + //} +#if DEBUG + [Fact] + public async Task WithKeys() + { + using var form = new OptionsForm( + new Settings + { + AcceptAllHotKey = new HotKey + { + Shift = true, + Key = "A" + } + }, + x => Task.FromResult>(new List())); + await Verifier.Verify(form); + } + + [Fact] + public async Task Default() + { + using var form = new OptionsForm( + new Settings(), + x => Task.FromResult>(new List())); + await Verifier.Verify(form); + } +#endif +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/PiperTest.Delete.verified.txt b/src/DiffEngineTray.Mac.Tests/PiperTest.Delete.verified.txt new file mode 100644 index 00000000..a3a5e935 --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/PiperTest.Delete.verified.txt @@ -0,0 +1,3 @@ +{ + File: 'Foo' +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/PiperTest.Move.verified.txt b/src/DiffEngineTray.Mac.Tests/PiperTest.Move.verified.txt new file mode 100644 index 00000000..e8428b79 --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/PiperTest.Move.verified.txt @@ -0,0 +1,8 @@ +{ + Temp: 'Foo', + Target: 'Bar', + Exe: 'theExe', + Arguments: 'TheArguments "s"', + CanKill: true, + ProcessId: 10 +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/PiperTest.SendOnly.verified.txt b/src/DiffEngineTray.Mac.Tests/PiperTest.SendOnly.verified.txt new file mode 100644 index 00000000..34878269 --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/PiperTest.SendOnly.verified.txt @@ -0,0 +1,37 @@ +[ + 'Failed to send payload to DiffEngineTray. + +Payload: +{ +"Type":"Move", +"Exe":"theExe", +"Arguments":"TheArguments \"s\"", +"CanKill":true, +"ProcessId":10 +} + +Exception: +System.Net.Internals.SocketExceptionFactory+ExtendedSocketException (10061): No connection could be made because the target machine actively refused it. [::ffff:127.0.0.1]:3492 + at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(Exception source) + at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult) + at System.Net.Sockets.TcpClient.EndConnect(IAsyncResult asyncResult) + at System.Net.Sockets.TcpClient.<>c.b__27_1(IAsyncResult asyncResult) + at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization) +--- End of stack trace from previous location where exception was thrown --- + 'Failed to send payload to DiffEngineTray. + +Payload: +{ +"Type":"Delete", +} + + +Exception: +System.Net.Internals.SocketExceptionFactory+ExtendedSocketException (10061): No connection could be made because the target machine actively refused it. [::ffff:127.0.0.1]:3492 + at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(Exception source) + at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult) + at System.Net.Sockets.TcpClient.EndConnect(IAsyncResult asyncResult) + at System.Net.Sockets.TcpClient.<>c.b__27_1(IAsyncResult asyncResult) + at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization) +--- End of stack trace from previous location where exception was thrown --- +] \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/PiperTest.cs b/src/DiffEngineTray.Mac.Tests/PiperTest.cs new file mode 100644 index 00000000..b4d68e33 --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/PiperTest.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using VerifyTests; +using VerifyXunit; +using Xunit; +using Xunit.Abstractions; + +[UsesVerify] +public class PiperTest : + XunitContextBase +{ + [Fact] + public async Task Delete() + { + DeletePayload received = null!; + var source = new CancellationTokenSource(); + //var task = PiperServer.Start(s => { }, s => received = s, source.Token); + await PiperClient.SendDeleteAsync("Foo", source.Token); + await Task.Delay(1000); + source.Cancel(); + //await task; + await Verifier.Verify(received); + } + + [Fact] + public async Task Move() + { + MovePayload received = null!; + var source = new CancellationTokenSource(); + var task = PiperServer.Start(s => received = s, s => { }, source.Token); + await PiperClient.SendMoveAsync("Foo", "Bar", "theExe", "TheArguments \"s\"", true, 10, source.Token); + await Task.Delay(1000); + source.Cancel(); + await task; + await Verifier.Verify(received); + } + + [Fact] + public async Task SendOnly() + { + var file = Path.GetFullPath("temp.txt"); + File.Delete(file); + await File.WriteAllTextAsync(file, "a"); + try + { + await PiperClient.SendMoveAsync(file, file, "theExe", "TheArguments \"s\"", true, 10); + await PiperClient.SendDeleteAsync(file); + } + catch (InvalidOperationException) + { + } + + var settings = new VerifySettings(); + settings.ScrubLinesContaining("temp.txt"); + //TODO: add "scrub source dir" to verify and remove the below + settings.ScrubLinesContaining("PiperClient"); + await Verifier.Verify(Logs, settings); + } + + public PiperTest(ITestOutputHelper output) : + base(output) + { + } +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/RecordingTracker.cs b/src/DiffEngineTray.Mac.Tests/RecordingTracker.cs new file mode 100644 index 00000000..c65444e3 --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/RecordingTracker.cs @@ -0,0 +1,17 @@ +using Xunit; + +class RecordingTracker : + Tracker +{ + public RecordingTracker() : + base(() => {}, () => {}) + { + } + + public void AssertEmpty() + { + Assert.Empty(Deletes); + Assert.Empty(Moves); + Assert.False(TrackingAny); + } +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/SettingsHelperTests.ReadWrite.verified.txt b/src/DiffEngineTray.Mac.Tests/SettingsHelperTests.ReadWrite.verified.txt new file mode 100644 index 00000000..69187cdd --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/SettingsHelperTests.ReadWrite.verified.txt @@ -0,0 +1,5 @@ +{ + AcceptAllHotKey: { + Key: 'T' + } +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/SettingsHelperTests.cs b/src/DiffEngineTray.Mac.Tests/SettingsHelperTests.cs new file mode 100644 index 00000000..b6b4e837 --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/SettingsHelperTests.cs @@ -0,0 +1,40 @@ +using System.IO; +using System.Threading.Tasks; +using VerifyXunit; +using Xunit; +using Xunit.Abstractions; + +[UsesVerify] +public class SettingsHelperTests : + XunitContextBase +{ + [Fact] + public async Task ReadWrite() + { + var tempFile = "ReadWrite.txt"; + File.Delete(tempFile); + try + { + SettingsHelper.FilePath = tempFile; + await SettingsHelper.Write( + new Settings + { + AcceptAllHotKey = new HotKey + { + Key = "T" + } + }); + var result = await SettingsHelper.Read(); + await Verifier.Verify(result); + } + finally + { + File.Delete(tempFile); + } + } + + public SettingsHelperTests(ITestOutputHelper output) : + base(output) + { + } +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/TrackerClearTest.cs b/src/DiffEngineTray.Mac.Tests/TrackerClearTest.cs new file mode 100644 index 00000000..31a00fe3 --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/TrackerClearTest.cs @@ -0,0 +1,35 @@ +using System.IO; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +public class TrackerClearTest : + XunitContextBase +{ + [Fact] + public async Task Simple() + { + await using var tracker = new RecordingTracker(); + tracker.AddDelete(file1); + tracker.AddMove(file2, file2, "theExe", "theArguments", true, null); + tracker.Clear(); + tracker.AssertEmpty(); + } + + public TrackerClearTest(ITestOutputHelper output) : + base(output) + { + file1 = Path.GetTempFileName(); + file2 = Path.GetTempFileName(); + } + + public override void Dispose() + { + File.Delete(file1); + File.Delete(file2); + base.Dispose(); + } + + string file1; + string file2; +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/TrackerDeleteTest.cs b/src/DiffEngineTray.Mac.Tests/TrackerDeleteTest.cs new file mode 100644 index 00000000..e3254225 --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/TrackerDeleteTest.cs @@ -0,0 +1,107 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +public class TrackerDeleteTest : + XunitContextBase +{ + [Fact] + public async Task AddSingle() + { + await using var tracker = new RecordingTracker(); + tracker.AddDelete(file1); + Assert.Equal(1, tracker.Deletes.Count); + Assert.True(tracker.TrackingAny); + } + + [Fact] + public async Task AddSingle_BackgroundDelete() + { + await using var tracker = new RecordingTracker(); + tracker.AddDelete(file1); + File.Delete(file1); + Thread.Sleep(3000); + tracker.AssertEmpty(); + } + + [Fact] + public async Task AddMultiple() + { + await using var tracker = new RecordingTracker(); + tracker.AddDelete(file1); + tracker.AddDelete(file2); + Assert.Equal(2, tracker.Deletes.Count); + Assert.True(tracker.TrackingAny); + } + + [Fact] + public void AddSame() + { + var tracker = new RecordingTracker(); + tracker.AddDelete(file1); + tracker.AddDelete(file1); + Assert.Equal(1, tracker.Deletes.Count); + Assert.True(tracker.TrackingAny); + } + + [Fact] + public async Task AcceptAllSingle() + { + await using var tracker = new RecordingTracker(); + tracker.AddDelete(file1); + tracker.AcceptAll(); + tracker.AssertEmpty(); + } + + [Fact] + public async Task AcceptAllMultiple() + { + await using var tracker = new RecordingTracker(); + tracker.AddDelete(file1); + tracker.AddDelete(file2); + tracker.AcceptAll(); + tracker.AssertEmpty(); + } + + [Fact] + public async Task AcceptSingle() + { + await using var tracker = new RecordingTracker(); + var tracked = tracker.AddDelete(file1); + tracker.Accept(tracked); + tracker.AssertEmpty(); + } + + [Fact] + public async Task AcceptSingle_NotEmpty() + { + await using var tracker = new RecordingTracker(); + var tracked = tracker.AddDelete(file1); + tracker.AddDelete(file2); + tracker.Accept(tracked); + Assert.Equal(1, tracker.Deletes.Count); + Assert.True(tracker.TrackingAny); + } + + public TrackerDeleteTest(ITestOutputHelper output) : + base(output) + { + file1 = Path.GetTempFileName(); + file2 = Path.GetTempFileName(); + file3 = Path.GetTempFileName(); + } + + public override void Dispose() + { + File.Delete(file1); + File.Delete(file2); + File.Delete(file3); + base.Dispose(); + } + + string file1; + string file2; + string file3; +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/TrackerMoveTest.cs b/src/DiffEngineTray.Mac.Tests/TrackerMoveTest.cs new file mode 100644 index 00000000..f20fb057 --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/TrackerMoveTest.cs @@ -0,0 +1,111 @@ +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +public class TrackerMoveTest : + XunitContextBase +{ + [Fact] + public async Task AddSingle() + { + await using var tracker = new RecordingTracker(); + tracker.AddMove(file1, file1, "theExe", "theArguments", true, null); + Assert.Equal(1, tracker.Moves.Count); + Assert.True(tracker.TrackingAny); + } + + [Fact] + public async Task AddMultiple() + { + await using var tracker = new RecordingTracker(); + tracker.AddMove(file1, file1, "theExe", "theArguments", true, null); + tracker.AddMove(file2, file2, "theExe", "theArguments", true, null); + Assert.Equal(2, tracker.Moves.Count); + Assert.True(tracker.TrackingAny); + } + + [Fact] + public async Task AddSame() + { + await using var tracker = new RecordingTracker(); + tracker.AddMove(file1, file1, "theExe", "theArguments", true, null); + using var process = Process.GetCurrentProcess(); + var processId = process.Id; + var tracked = tracker.AddMove(file1, file1, "theExe", "theArguments", true, processId); + Assert.Equal(1, tracker.Moves.Count); + Assert.Equal(process.Id, tracked.Process!.Id); + Assert.True(tracker.TrackingAny); + } + + [Fact] + public async Task AcceptAllSingle() + { + await using var tracker = new RecordingTracker(); + tracker.AddMove(file1, file1, "theExe", "theArguments", true, null); + tracker.AcceptAll(); + tracker.AssertEmpty(); + } + + [Fact] + public async Task AcceptAllMultiple() + { + await using var tracker = new RecordingTracker(); + tracker.AddMove(file1, file1, "theExe", "theArguments", true, null); + tracker.AddMove(file2, file2, "theExe", "theArguments", true, null); + tracker.AcceptAll(); + tracker.AssertEmpty(); + } + + [Fact] + public async Task AcceptSingle() + { + await using var tracker = new RecordingTracker(); + var tracked = tracker.AddMove(file1, file1, "theExe", "theArguments", true, null); + tracker.Accept(tracked); + tracker.AssertEmpty(); + } + + [Fact] + public async Task AddSingle_BackgroundDelete() + { + await using var tracker = new RecordingTracker(); + tracker.AddMove(file1, file1, "theExe", "theArguments", true, null); + File.Delete(file1); + Thread.Sleep(3000); + tracker.AssertEmpty(); + } + + [Fact] + public void AcceptSingle_NotEmpty() + { + var tracker = new RecordingTracker(); + var tracked = tracker.AddMove(file1, file1, "theExe", "theArguments", true, null); + tracker.AddMove(file2, file2, "theExe", "theArguments", true, null); + tracker.Accept(tracked); + Assert.Equal(1, tracker.Moves.Count); + Assert.True(tracker.TrackingAny); + } + + public TrackerMoveTest(ITestOutputHelper output) : + base(output) + { + file1 = Path.GetTempFileName(); + file2 = Path.GetTempFileName(); + file3 = Path.GetTempFileName(); + } + + public override void Dispose() + { + File.Delete(file1); + File.Delete(file2); + File.Delete(file3); + base.Dispose(); + } + + string file1; + string file2; + string file3; +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.Tests/VersionReaderTest.cs b/src/DiffEngineTray.Mac.Tests/VersionReaderTest.cs new file mode 100644 index 00000000..cf2656ed --- /dev/null +++ b/src/DiffEngineTray.Mac.Tests/VersionReaderTest.cs @@ -0,0 +1,18 @@ +using Xunit; +using Xunit.Abstractions; + +public class VersionReaderTest : + XunitContextBase +{ + [Fact] + public void AddSingle() + { + Assert.NotEmpty(VersionReader.VersionString); + Assert.NotNull(VersionReader.VersionString); + } + + public VersionReaderTest(ITestOutputHelper output) : + base(output) + { + } +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac.sln b/src/DiffEngineTray.Mac.sln new file mode 100755 index 00000000..67aaf33b --- /dev/null +++ b/src/DiffEngineTray.Mac.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffEngineTray.Mac", "DiffEngineTray.Mac\DiffEngineTray.Mac.csproj", "{FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffEngineTray.Common", "DiffEngineTray.Common\DiffEngineTray.Common.csproj", "{CD26DF8E-447A-49CE-9B8C-C31E251FFC84}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffEngine", "DiffEngine\DiffEngine.csproj", "{0978620F-897F-4BF4-AB1E-D47B1A17C2F9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffEngine.Tests", "DiffEngine.Tests\DiffEngine.Tests.csproj", "{29435A62-0E93-42EA-8831-F72B4144C1EB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FakeDiffTool", "FakeDiffTool\FakeDiffTool.csproj", "{C614433F-1C56-4CA4-BE35-CD7B4D224A5C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffEngineTray.Mac.Tests", "DiffEngineTray.Mac.Tests\DiffEngineTray.Mac.Tests.csproj", "{74C91284-6F82-43B4-B4B7-BFDD67FB4F26}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}.Debug|x86.ActiveCfg = Debug|x86 + {FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}.Debug|x86.Build.0 = Debug|x86 + {FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}.Debug|x86.Deploy.0 = Debug|x86 + {FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}.Release|x86.ActiveCfg = Release|x86 + {FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}.Release|x86.Build.0 = Release|x86 + {FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}.Release|x86.Deploy.0 = Release|x86 + {CD26DF8E-447A-49CE-9B8C-C31E251FFC84}.Debug|x86.ActiveCfg = Debug|Any CPU + {CD26DF8E-447A-49CE-9B8C-C31E251FFC84}.Debug|x86.Build.0 = Debug|Any CPU + {CD26DF8E-447A-49CE-9B8C-C31E251FFC84}.Release|x86.ActiveCfg = Release|Any CPU + {CD26DF8E-447A-49CE-9B8C-C31E251FFC84}.Release|x86.Build.0 = Release|Any CPU + {0978620F-897F-4BF4-AB1E-D47B1A17C2F9}.Debug|x86.ActiveCfg = Debug|Any CPU + {0978620F-897F-4BF4-AB1E-D47B1A17C2F9}.Debug|x86.Build.0 = Debug|Any CPU + {0978620F-897F-4BF4-AB1E-D47B1A17C2F9}.Release|x86.ActiveCfg = Release|Any CPU + {0978620F-897F-4BF4-AB1E-D47B1A17C2F9}.Release|x86.Build.0 = Release|Any CPU + {29435A62-0E93-42EA-8831-F72B4144C1EB}.Debug|x86.ActiveCfg = Debug|Any CPU + {29435A62-0E93-42EA-8831-F72B4144C1EB}.Debug|x86.Build.0 = Debug|Any CPU + {29435A62-0E93-42EA-8831-F72B4144C1EB}.Release|x86.ActiveCfg = Release|Any CPU + {29435A62-0E93-42EA-8831-F72B4144C1EB}.Release|x86.Build.0 = Release|Any CPU + {C614433F-1C56-4CA4-BE35-CD7B4D224A5C}.Debug|x86.ActiveCfg = Debug|Any CPU + {C614433F-1C56-4CA4-BE35-CD7B4D224A5C}.Debug|x86.Build.0 = Debug|Any CPU + {C614433F-1C56-4CA4-BE35-CD7B4D224A5C}.Release|x86.ActiveCfg = Release|Any CPU + {C614433F-1C56-4CA4-BE35-CD7B4D224A5C}.Release|x86.Build.0 = Release|Any CPU + {74C91284-6F82-43B4-B4B7-BFDD67FB4F26}.Debug|x86.ActiveCfg = Debug|Any CPU + {74C91284-6F82-43B4-B4B7-BFDD67FB4F26}.Debug|x86.Build.0 = Debug|Any CPU + {74C91284-6F82-43B4-B4B7-BFDD67FB4F26}.Release|x86.ActiveCfg = Release|Any CPU + {74C91284-6F82-43B4-B4B7-BFDD67FB4F26}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/DiffEngineTray.Mac/App.cs b/src/DiffEngineTray.Mac/App.cs new file mode 100644 index 00000000..8c2aa167 --- /dev/null +++ b/src/DiffEngineTray.Mac/App.cs @@ -0,0 +1,56 @@ +using System.Diagnostics; +using AppKit; +using CoreGraphics; +using Xamarin.Forms; +using Xamarin.Forms.Platform.MacOS; + +namespace DiffEngineTray.Mac +{ + public class App : Application + { + NSViewController controller; + + public void CreateMainController() + { + MainPage = new NavigationPage(new MainPage()); + controller = MainPage.CreateViewController(); + controller.View.Frame = new CGRect(0, 0, 300, 400); + } + + protected override void OnStart() + { + base.OnStart(); + Debug.WriteLine("Application started"); + } + + protected override void OnSleep() + { + base.OnSleep(); + Debug.WriteLine("Application sleeps"); + } + + protected override void OnResume() + { + base.OnResume(); + Debug.WriteLine("Application resumes"); + } + + public void ShowSettings(NSStatusItem statusBarItem) + { + if(MainPage == null) + { + CreateMainController(); + Current.SendStart(); + } + + var popover = new NSPopover + { + ContentViewController = controller, + Behavior = NSPopoverBehavior.Transient, + Delegate = new PopoverDelegate() + }; + + popover.Show(statusBarItem.Button.Bounds, statusBarItem.Button, NSRectEdge.MaxYEdge); + } + } +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac/AppDelegate.cs b/src/DiffEngineTray.Mac/AppDelegate.cs new file mode 100755 index 00000000..a90bdd5c --- /dev/null +++ b/src/DiffEngineTray.Mac/AppDelegate.cs @@ -0,0 +1,100 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AppKit; +using Foundation; +using Xamarin.Forms; + +namespace DiffEngineTray.Mac +{ + [Register("AppDelegate")] + public class AppDelegate : NSApplicationDelegate + { + NSStatusItem statusBarItem; + PopupMenuItems popup; + App app; + + public AppDelegate() + { + Forms.Init(); + Application.SetCurrentApplication(app = new App()); + CreateStatusItems(); + SubscribeEvents(); + + IssueLauncher.Initialize(new MacMessageBox()); + } + + void SubscribeEvents() + { + popup.CloseApp = () => Exit(); + popup.ShowOptions = () => app.ShowSettings(statusBarItem); + } + + void CreateStatusItems() + { + // Create the status bar item + statusBarItem = PopupMenu.CreateStatusBarItem(); + statusBarItem.Button.Activated += StatusItemActivated; + + // Create the popup + popup = new PopupMenuItems(); + } + + void StatusItemActivated(object sender, EventArgs e) + { + statusBarItem.PopUpStatusItemMenu(popup); + } + + void Exit() + { + NSApplication.SharedApplication.Terminate(this); + } + + public override void DidFinishLaunching(NSNotification notification) + { + Task.Run(StartServer); + } + + public override void WillTerminate(NSNotification notification) + { + // Insert code here to tear down your application + } + + async Task StartServer() + { + var tokenSource = new CancellationTokenSource(); + var cancellation = tokenSource.Token; + + await using var tracker = new Tracker( + active: () => statusBarItem.Image = NSImage.ImageNamed("activate.ico"), + inactive: () => statusBarItem.Image = NSImage.ImageNamed("default.ico")); + + using var task = StartServer(tracker, cancellation); + } + + static Task StartServer(Tracker tracker, CancellationToken cancellation) + { + return PiperServer.Start( + payload => + { + tracker.AddMove( + payload.Temp, + payload.Target, + payload.Exe, + payload.Arguments, + payload.CanKill, + payload.ProcessId); + }, + payload => tracker.AddDelete(payload.File), + cancellation); + } + } + + public class PopoverDelegate : NSPopoverDelegate + { + public override void DidClose(NSNotification notification) + { + Application.Current.SendSleep(); + } + } +} diff --git a/src/DiffEngineTray.Mac/Assets.xcassets/AppIcon.appiconset/Contents.json b/src/DiffEngineTray.Mac/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100755 index 00000000..6b285452 --- /dev/null +++ b/src/DiffEngineTray.Mac/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images": [ + { + "filename": "AppIcon-16.png", + "size": "16x16", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-16@2x.png", + "size": "16x16", + "scale": "2x", + "idiom": "mac" + }, + { + "filename": "AppIcon-32.png", + "size": "32x32", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-32@2x.png", + "size": "32x32", + "scale": "2x", + "idiom": "mac" + }, + { + "filename": "AppIcon-128.png", + "size": "128x128", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-128@2x.png", + "size": "128x128", + "scale": "2x", + "idiom": "mac" + }, + { + "filename": "AppIcon-256.png", + "size": "256x256", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-256@2x.png", + "size": "256x256", + "scale": "2x", + "idiom": "mac" + }, + { + "filename": "AppIcon-512.png", + "size": "512x512", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-512@2x.png", + "size": "512x512", + "scale": "2x", + "idiom": "mac" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac/Assets.xcassets/Contents.json b/src/DiffEngineTray.Mac/Assets.xcassets/Contents.json new file mode 100755 index 00000000..4caf392f --- /dev/null +++ b/src/DiffEngineTray.Mac/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac/DiffEngineTray.Mac.csproj b/src/DiffEngineTray.Mac/DiffEngineTray.Mac.csproj new file mode 100755 index 00000000..67a66d25 --- /dev/null +++ b/src/DiffEngineTray.Mac/DiffEngineTray.Mac.csproj @@ -0,0 +1,124 @@ + + + + Debug + x86 + {FAFC7DE7-FACE-4947-8332-5D644DDC8DB7} + {A3F8F2AB-B479-4A4A-A458-A89E7DC349F1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Exe + DiffEngineTray.Mac + DiffEngineTray.Mac + v2.0 + Xamarin.Mac + Resources + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + Mac Developer + false + false + false + true + true + x86 + + + pdbonly + true + bin\Release + prompt + 4 + false + true + false + true + true + true + SdkOnly + x86 + + + + + + + + + + + + + + + + + + + + + + + MainPage.xaml + + + + SettingsPage.xaml + + + + + OptionsForm.xaml + + + + + + + + ViewController.cs + + + + + + + + + + + + + {cd26df8e-447a-49ce-9b8c-c31e251ffc84} + DiffEngineTray.Common + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/DiffEngineTray.Mac/Entitlements.plist b/src/DiffEngineTray.Mac/Entitlements.plist new file mode 100755 index 00000000..9ae59937 --- /dev/null +++ b/src/DiffEngineTray.Mac/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/DiffEngineTray.Mac/Info.plist b/src/DiffEngineTray.Mac/Info.plist new file mode 100755 index 00000000..4ad72122 --- /dev/null +++ b/src/DiffEngineTray.Mac/Info.plist @@ -0,0 +1,34 @@ + + + + + LSUIElement + + CFBundleName + DiffEngineTray.Mac + CFBundleIdentifier + com.companyname.DiffEngineTray.Mac + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + 10.15 + CFBundleDevelopmentRegion + en + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + ???? + NSHumanReadableCopyright + ${AuthorCopyright:HtmlEncode} + NSPrincipalClass + NSApplication + NSMainStoryboardFile + Main + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + diff --git a/src/DiffEngineTray.Mac/MacMessageBox.cs b/src/DiffEngineTray.Mac/MacMessageBox.cs new file mode 100644 index 00000000..4a6bae96 --- /dev/null +++ b/src/DiffEngineTray.Mac/MacMessageBox.cs @@ -0,0 +1,49 @@ +using System; +using AppKit; +using CoreGraphics; +using DiffEngineTray.Common; + +namespace DiffEngineTray.Mac +{ + public class MacMessageBox : IMessageBox + { + public static bool? ShowMessage(string message, string title, MessageBoxIcon icon, MessageBoxButtons buttons = MessageBoxButtons.YesNo) + { + return new MacMessageBox().Show(message, title, icon); + } + + public bool? Show(string message, string title, MessageBoxIcon icon, MessageBoxButtons buttons = MessageBoxButtons.YesNo) + { + var alert = new NSAlert(); + alert.AlertStyle = NSAlertStyle.Warning; + alert.MessageText = title; + alert.InformativeText = message; + alert.Icon = NSImage.ImageNamed("error.png"); + alert.Icon.Size = new CGSize(16, 16); + + AddButtons(alert, buttons); + + var result = alert.RunModal(); + + return TranslateResult(result, buttons); + } + + private bool? TranslateResult(nint result, MessageBoxButtons buttons) + { + return result == 1000; + } + + private void AddButtons(NSAlert alert, MessageBoxButtons butons) + { + if (butons == MessageBoxButtons.YesNo) + { + alert.AddButton("Yes"); + alert.AddButton("No"); + } + else + { + alert.AddButton("OK"); + } + } + } +} \ No newline at end of file diff --git a/src/DiffEngineTray.Mac/Main.cs b/src/DiffEngineTray.Mac/Main.cs new file mode 100755 index 00000000..8c136513 --- /dev/null +++ b/src/DiffEngineTray.Mac/Main.cs @@ -0,0 +1,13 @@ +using AppKit; + +namespace DiffEngineTray.Mac +{ + static class MainClass + { + static void Main(string[] args) + { + NSApplication.Init(); + NSApplication.Main(args); + } + } +} diff --git a/src/DiffEngineTray.Mac/Main.storyboard b/src/DiffEngineTray.Mac/Main.storyboard new file mode 100755 index 00000000..4cb7c482 --- /dev/null +++ b/src/DiffEngineTray.Mac/Main.storyboard @@ -0,0 +1,681 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/DiffEngineTray.Mac/MainPage.xaml b/src/DiffEngineTray.Mac/MainPage.xaml new file mode 100644 index 00000000..bc7243ce --- /dev/null +++ b/src/DiffEngineTray.Mac/MainPage.xaml @@ -0,0 +1,12 @@ + + + + + +