From fc0a8af970a0b4711823c7a8e6cf3e0b36252685 Mon Sep 17 00:00:00 2001 From: Hadi Eskandari Date: Sat, 24 Oct 2020 10:55:30 +1100 Subject: [PATCH 01/14] Osx tray app (#120) * moving the non-windows stuff out * mac app skeleton * base menu structure and popup window * mac messagebox impl * mac tray app skeleton --- src/DiffEngine.Tests/DiffToolsTest.cs | 20 + src/DiffEngine.Tests/MacDiffTool.cs | 22 + src/DiffEngine.sln | 8 + src/DiffEngine/DiffEngine.csproj | 4 +- .../AssemblyLocation.cs | 0 .../AsyncTimer.cs | 0 .../DiffEngineTray.Common.csproj | 19 + .../DirectoryLauncher.cs | 0 .../ExceptionHandler.cs | 0 .../FileComparer.cs | 4 +- src/DiffEngineTray.Common/IMessageBox.cs | 25 + src/DiffEngineTray.Common/IUpdater.cs | 7 + .../InternalsVisibleTo.cs | 7 + .../IssueLauncher.cs | 17 +- .../JsonEscaping.cs | 0 .../LinkLauncher.cs | 0 .../Logging.cs | 0 .../Payloads/DeletePayload.cs | 0 .../Payloads/MovePayload.cs | 0 .../PiperClient.cs | 0 .../PiperServer.cs | 0 .../ProcessEx.cs | 0 .../Serializer.cs | 0 .../TrackedDelete.cs | 0 .../TrackedMove.cs | 0 .../Tracker.cs | 2 +- .../VersionReader.cs | 0 .../AsyncTimerTests.cs | 122 ++++ .../DiffEngineTray.Mac.Tests.csproj | 42 ++ src/DiffEngineTray.Mac.Tests/FodyWeavers.xml | 3 + .../HotKeyControlTests.Default.verified.png | Bin 0 -> 2779 bytes .../HotKeyControlTests.WithKeys.verified.png | Bin 0 -> 2282 bytes .../HotKeyControlTests.cs | 37 + .../MenuBuilderTest.Empty.verified.png | Bin 0 -> 3280 bytes .../MenuBuilderTest.Full.verified.png | Bin 0 -> 8871 bytes .../MenuBuilderTest.OnlyDelete.verified.png | Bin 0 -> 6408 bytes .../MenuBuilderTest.OnlyMove.verified.png | Bin 0 -> 6506 bytes .../MenuBuilderTest.cs | 78 ++ .../ModuleInitializer.cs | 11 + .../OptionsFormTests.Default.verified.png | Bin 0 -> 14735 bytes .../OptionsFormTests.WithKeys.verified.png | Bin 0 -> 14225 bytes .../OptionsFormTests.cs | 60 ++ .../PiperTest.Delete.verified.txt | 3 + .../PiperTest.Move.verified.txt | 8 + .../PiperTest.SendOnly.verified.txt | 37 + src/DiffEngineTray.Mac.Tests/PiperTest.cs | 66 ++ .../RecordingTracker.cs | 17 + ...SettingsHelperTests.ReadWrite.verified.txt | 5 + .../SettingsHelperTests.cs | 40 + .../TrackerClearTest.cs | 35 + .../TrackerDeleteTest.cs | 107 +++ .../TrackerMoveTest.cs | 111 +++ .../VersionReaderTest.cs | 18 + src/DiffEngineTray.Mac.sln | 48 ++ src/DiffEngineTray.Mac/App.cs | 56 ++ src/DiffEngineTray.Mac/AppDelegate.cs | 100 +++ .../AppIcon.appiconset/Contents.json | 68 ++ .../Assets.xcassets/Contents.json | 6 + .../DiffEngineTray.Mac.csproj | 124 ++++ src/DiffEngineTray.Mac/Entitlements.plist | 6 + src/DiffEngineTray.Mac/Info.plist | 34 + src/DiffEngineTray.Mac/MacMessageBox.cs | 49 ++ src/DiffEngineTray.Mac/Main.cs | 13 + src/DiffEngineTray.Mac/Main.storyboard | 681 ++++++++++++++++++ src/DiffEngineTray.Mac/MainPage.xaml | 12 + src/DiffEngineTray.Mac/MainPage.xaml.cs | 25 + src/DiffEngineTray.Mac/PopupMenu.cs | 89 +++ src/DiffEngineTray.Mac/Resources/accept.png | Bin 0 -> 723 bytes .../Resources/acceptAll.png | Bin 0 -> 388 bytes src/DiffEngineTray.Mac/Resources/active.ico | Bin 0 -> 32038 bytes src/DiffEngineTray.Mac/Resources/clear.png | Bin 0 -> 656 bytes src/DiffEngineTray.Mac/Resources/cogs.png | Bin 0 -> 872 bytes src/DiffEngineTray.Mac/Resources/default.ico | Bin 0 -> 32038 bytes src/DiffEngineTray.Mac/Resources/delete.png | Bin 0 -> 712 bytes src/DiffEngineTray.Mac/Resources/error.png | Bin 0 -> 8614 bytes src/DiffEngineTray.Mac/Resources/exit.png | Bin 0 -> 536 bytes src/DiffEngineTray.Mac/Resources/folder.png | Bin 0 -> 585 bytes src/DiffEngineTray.Mac/Resources/icon.ico | Bin 0 -> 93062 bytes src/DiffEngineTray.Mac/Resources/icon.png | Bin 0 -> 10458 bytes src/DiffEngineTray.Mac/Resources/link.png | Bin 0 -> 577 bytes .../Settings/HotKeyControl.Designer.cs | 212 ++++++ .../Settings/HotKeyControl.cs | 70 ++ .../Settings/HotKeyControl.resx | 60 ++ .../Settings/OptionsForm.xaml | 13 + .../Settings/OptionsForm.xaml.cs | 76 ++ .../Settings/OptionsFormLauncher.cs | 61 ++ src/DiffEngineTray.Mac/Settings/Settings.cs | 6 + .../Settings/SettingsHelper.cs | 36 + .../Settings/SettingsValidator.cs | 25 + src/DiffEngineTray.Mac/SettingsPage.xaml | 13 + src/DiffEngineTray.Mac/SettingsPage.xaml.cs | 20 + src/DiffEngineTray.Mac/ViewController.cs | 34 + .../ViewController.designer.cs | 18 + src/DiffEngineTray/DiffEngineTray.csproj | 5 + src/DiffEngineTray/Program.cs | 3 + src/DiffEngineTray/Settings/OptionsForm.cs | 6 +- .../{Updater.cs => WindowsAppUpdater.cs} | 5 +- src/DiffEngineTray/WindowsMessageBox.cs | 30 + 98 files changed, 2925 insertions(+), 14 deletions(-) create mode 100644 src/DiffEngine.Tests/MacDiffTool.cs rename src/{DiffEngineTray => DiffEngineTray.Common}/AssemblyLocation.cs (100%) rename src/{DiffEngineTray => DiffEngineTray.Common}/AsyncTimer.cs (100%) create mode 100644 src/DiffEngineTray.Common/DiffEngineTray.Common.csproj rename src/{DiffEngineTray => DiffEngineTray.Common}/DirectoryLauncher.cs (100%) rename src/{DiffEngineTray => DiffEngineTray.Common}/ExceptionHandler.cs (100%) rename src/{DiffEngineTray => DiffEngineTray.Common}/FileComparer.cs (95%) create mode 100644 src/DiffEngineTray.Common/IMessageBox.cs create mode 100644 src/DiffEngineTray.Common/IUpdater.cs create mode 100644 src/DiffEngineTray.Common/InternalsVisibleTo.cs rename src/{DiffEngineTray => DiffEngineTray.Common}/IssueLauncher.cs (83%) rename src/{DiffEngineTray => DiffEngineTray.Common}/JsonEscaping.cs (100%) rename src/{DiffEngineTray => DiffEngineTray.Common}/LinkLauncher.cs (100%) rename src/{DiffEngineTray => DiffEngineTray.Common}/Logging.cs (100%) rename src/{DiffEngineTray => DiffEngineTray.Common}/Payloads/DeletePayload.cs (100%) rename src/{DiffEngineTray => DiffEngineTray.Common}/Payloads/MovePayload.cs (100%) rename src/{DiffEngineTray => DiffEngineTray.Common}/PiperClient.cs (100%) rename src/{DiffEngineTray => DiffEngineTray.Common}/PiperServer.cs (100%) rename src/{DiffEngineTray => DiffEngineTray.Common}/ProcessEx.cs (100%) rename src/{DiffEngineTray => DiffEngineTray.Common}/Serializer.cs (100%) rename src/{DiffEngineTray => DiffEngineTray.Common}/TrackedDelete.cs (100%) rename src/{DiffEngineTray => DiffEngineTray.Common}/TrackedMove.cs (100%) rename src/{DiffEngineTray => DiffEngineTray.Common}/Tracker.cs (98%) rename src/{DiffEngineTray => DiffEngineTray.Common}/VersionReader.cs (100%) create mode 100644 src/DiffEngineTray.Mac.Tests/AsyncTimerTests.cs create mode 100644 src/DiffEngineTray.Mac.Tests/DiffEngineTray.Mac.Tests.csproj create mode 100644 src/DiffEngineTray.Mac.Tests/FodyWeavers.xml create mode 100644 src/DiffEngineTray.Mac.Tests/HotKeyControlTests.Default.verified.png create mode 100644 src/DiffEngineTray.Mac.Tests/HotKeyControlTests.WithKeys.verified.png create mode 100644 src/DiffEngineTray.Mac.Tests/HotKeyControlTests.cs create mode 100644 src/DiffEngineTray.Mac.Tests/MenuBuilderTest.Empty.verified.png create mode 100644 src/DiffEngineTray.Mac.Tests/MenuBuilderTest.Full.verified.png create mode 100644 src/DiffEngineTray.Mac.Tests/MenuBuilderTest.OnlyDelete.verified.png create mode 100644 src/DiffEngineTray.Mac.Tests/MenuBuilderTest.OnlyMove.verified.png create mode 100644 src/DiffEngineTray.Mac.Tests/MenuBuilderTest.cs create mode 100644 src/DiffEngineTray.Mac.Tests/ModuleInitializer.cs create mode 100644 src/DiffEngineTray.Mac.Tests/OptionsFormTests.Default.verified.png create mode 100644 src/DiffEngineTray.Mac.Tests/OptionsFormTests.WithKeys.verified.png create mode 100644 src/DiffEngineTray.Mac.Tests/OptionsFormTests.cs create mode 100644 src/DiffEngineTray.Mac.Tests/PiperTest.Delete.verified.txt create mode 100644 src/DiffEngineTray.Mac.Tests/PiperTest.Move.verified.txt create mode 100644 src/DiffEngineTray.Mac.Tests/PiperTest.SendOnly.verified.txt create mode 100644 src/DiffEngineTray.Mac.Tests/PiperTest.cs create mode 100644 src/DiffEngineTray.Mac.Tests/RecordingTracker.cs create mode 100644 src/DiffEngineTray.Mac.Tests/SettingsHelperTests.ReadWrite.verified.txt create mode 100644 src/DiffEngineTray.Mac.Tests/SettingsHelperTests.cs create mode 100644 src/DiffEngineTray.Mac.Tests/TrackerClearTest.cs create mode 100644 src/DiffEngineTray.Mac.Tests/TrackerDeleteTest.cs create mode 100644 src/DiffEngineTray.Mac.Tests/TrackerMoveTest.cs create mode 100644 src/DiffEngineTray.Mac.Tests/VersionReaderTest.cs create mode 100755 src/DiffEngineTray.Mac.sln create mode 100644 src/DiffEngineTray.Mac/App.cs create mode 100755 src/DiffEngineTray.Mac/AppDelegate.cs create mode 100755 src/DiffEngineTray.Mac/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100755 src/DiffEngineTray.Mac/Assets.xcassets/Contents.json create mode 100755 src/DiffEngineTray.Mac/DiffEngineTray.Mac.csproj create mode 100755 src/DiffEngineTray.Mac/Entitlements.plist create mode 100755 src/DiffEngineTray.Mac/Info.plist create mode 100644 src/DiffEngineTray.Mac/MacMessageBox.cs create mode 100755 src/DiffEngineTray.Mac/Main.cs create mode 100755 src/DiffEngineTray.Mac/Main.storyboard create mode 100644 src/DiffEngineTray.Mac/MainPage.xaml create mode 100644 src/DiffEngineTray.Mac/MainPage.xaml.cs create mode 100644 src/DiffEngineTray.Mac/PopupMenu.cs create mode 100644 src/DiffEngineTray.Mac/Resources/accept.png create mode 100644 src/DiffEngineTray.Mac/Resources/acceptAll.png create mode 100644 src/DiffEngineTray.Mac/Resources/active.ico create mode 100644 src/DiffEngineTray.Mac/Resources/clear.png create mode 100644 src/DiffEngineTray.Mac/Resources/cogs.png create mode 100644 src/DiffEngineTray.Mac/Resources/default.ico create mode 100644 src/DiffEngineTray.Mac/Resources/delete.png create mode 100644 src/DiffEngineTray.Mac/Resources/error.png create mode 100644 src/DiffEngineTray.Mac/Resources/exit.png create mode 100644 src/DiffEngineTray.Mac/Resources/folder.png create mode 100755 src/DiffEngineTray.Mac/Resources/icon.ico create mode 100755 src/DiffEngineTray.Mac/Resources/icon.png create mode 100644 src/DiffEngineTray.Mac/Resources/link.png create mode 100644 src/DiffEngineTray.Mac/Settings/HotKeyControl.Designer.cs create mode 100644 src/DiffEngineTray.Mac/Settings/HotKeyControl.cs create mode 100644 src/DiffEngineTray.Mac/Settings/HotKeyControl.resx create mode 100644 src/DiffEngineTray.Mac/Settings/OptionsForm.xaml create mode 100644 src/DiffEngineTray.Mac/Settings/OptionsForm.xaml.cs create mode 100644 src/DiffEngineTray.Mac/Settings/OptionsFormLauncher.cs create mode 100644 src/DiffEngineTray.Mac/Settings/Settings.cs create mode 100644 src/DiffEngineTray.Mac/Settings/SettingsHelper.cs create mode 100644 src/DiffEngineTray.Mac/Settings/SettingsValidator.cs create mode 100644 src/DiffEngineTray.Mac/SettingsPage.xaml create mode 100644 src/DiffEngineTray.Mac/SettingsPage.xaml.cs create mode 100755 src/DiffEngineTray.Mac/ViewController.cs create mode 100755 src/DiffEngineTray.Mac/ViewController.designer.cs rename src/DiffEngineTray/{Updater.cs => WindowsAppUpdater.cs} (87%) create mode 100644 src/DiffEngineTray/WindowsMessageBox.cs diff --git a/src/DiffEngine.Tests/DiffToolsTest.cs b/src/DiffEngine.Tests/DiffToolsTest.cs index b7585797..369c603e 100644 --- a/src/DiffEngine.Tests/DiffToolsTest.cs +++ b/src/DiffEngine.Tests/DiffToolsTest.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Threading.Tasks; using DiffEngine; +using DiffEngine.Tests; using Xunit; using Xunit.Abstractions; @@ -37,6 +38,25 @@ public void AddTool() Assert.Equal(resolvedTool.Name, forExtension!.Name); } + [Fact] + public void MacDiffOrderShouldNotMessWithAddTool() + { + 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..82777019 --- /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/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 0000000000000000000000000000000000000000..ee8d4bd8389891d6a1c2470679ecd9e0cb50bdcd GIT binary patch literal 2779 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..46f617423eb11f4b86cff6388f6b69829df18a0a GIT binary patch literal 3280 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0f02ef4b3b60c978167a8bcc875f078070203eac GIT binary patch literal 8871 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..aaed2d162df62e8bf5f0ceccf25dbb46f77185b5 GIT binary patch literal 6408 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 H_wfG%``3Y} literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e0caa2f69baae7596016e88f9c9ab2c654367132 GIT binary patch literal 6506 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 { }, () => { }, 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 0000000000000000000000000000000000000000..1928a66647365d5ccce4644222b5948dbaed62bf GIT binary patch literal 14735 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..68448324065034b231da6cdbebb6e2092cce139b GIT binary patch literal 14225 zcmb_@2Ut_vw(i10u@SMLB1A+$rHLq^vw^Lkh*;?%A_5}4gdPG?6$O!2%rX9djKPx|*Y)@AI<^Y{ zfW21?{=5YMJaq79k)Id5!_H>*fq!_sZt3d+#a+jz!7n=;bc}QWpfqmxy3J1TyMVia zxfcNJYuWtaX?M$g001zlD}U-nGTD%apeHuNQfa8jucE&dHKs5^2uS8_-rtk*+kp_C_!G&)*Y&J=*W)qk73ztz zpd?y+m#0k22W!^#@qFCxgQh&dA3H{ZnXHqob`zzOi0uHdKR0}J`hf&EB>rl&Hn*7u z04KI3LeSJ<@M#D?gbD!ts{^+uDVX)OuzA8L()&ZEkt2B~-b8>dJ!z`8wk{lt(`HL= z^rE;JZatfdQYzmA0E%lLx z*9UY0j%`awc8%wqPfb5d1OUi^ltY+?-zes9ro3xH#Tge%RIggpDpXR3GiAK~jLnJT zsxzetbn^TY%zC?BGXQWiA-5?09>qij{d+cfC$w<85MK@OU*5vJosaj9z1MLj#pl(qjk)*2Jol ztqV9#b*I|ZI5dSK)5LZzoqMcKw-Z?09jw}S)EDC`3jmxKCs6dt>S5b%!n~Ne3_((E z<4pi1y$iKCFI%F${=4N>rTj#%T83%6G;Kn`{WI+!Ya}o`QlH0izriS&;B^8n3e9pL z4p!~(qvKBKSl4T<-PTSH9$hy?sM_@VH=*PNW^Lp6$e2Lcl$&S@%Fsae|)X+*EtR9i-Iz?{Qr=bJLgyI4J6-8#&sX~INE5WY^#UUa*5ZW?WjA0myW_)#U z)`OjlM=Flu{dS-i=#z)-`o@JEIWgzkGh}DFi73O2Z4cOGYIc|k7t!s&cWKP(7gLHM zmN1^7A-!RkypAw+tC9X?daF#1bCztkQ41S}{?g&Dklm6Wz^jtmvk>LD!$|w5$bW3m zprPAERylfQ$@(1j7&TE1W30PEjH{Jyo=cX`(g~Qo;*V0^O?#hqT!Lpks1)lbTI|qn zhdgz*T@tC~u1seBlogmwQ8%xbui6f*44f60*O3e5w28RgIjBUxt2exLJ}2b~l&Kg5 z7cp#aL-vC&hIYxs0x|wI{XM^iePUICQzi=_bG3=vUxA8hU-#cxIX}zT{eS)$mGv9m2j@8c|7_jB^;X znpd9@OQYGwFlKAmm@a0emh0Gby#8Hn&=LKNzw!d_IRR%KNKJ6|aUoF=!*wpjFimld9WgN?CpQp!!d?$a=_Gb`TQ8>6JAhW zTr{$Fm7wa6I(vp)8HG`%oz2q0uiBKcF#~Lb!S)sI0sT%+oK!i@oBc$%qfD^j^#Ypi z!PH)|o&QE|@eH8Qh+*?R^`byy@LJe3$MJ3OLbGKgG#i2WOp3uqxWi?b4OBM;gVf%t zt&N!EvNkFf+X?K^-qF^TX~b%79Sw$V*Ie$?HsxJ~y&^H!2hctk?i4o12Hm~D2{qRb zoGc4LeI5esQGme<>w1=i?=LF3LtV9D&V^Z6S|sb6=f}Bz>-xYF_Q`$CM7Q|Ao4!fY z;Qk@Hn|4aD&6U)0l>hxpf=ldzE`lg z)VtHXMf%Y0O$Nb_qE2>22sfV(|YR?&@}_wXhfaQUI1_o zO(g1iS}vwS_<(zF3#|@^tAO6!VWR9rD-Uo)MJ9Qt_&%Vq(Y#ic-ve~sT^)8h(c6J@ z?k4iPb&mrfq2-?Dn~ru*S|aiBW*0yHjV>-NhzI}t^fK|&{coV#K5rr)QyIQG))Hk| z8^Acb*}c%_tq8x=EY;RYS2eNPo`*K)%R9ielfmA$3(`wdyOtPh{m=CezzPy*qt0bR ze35sF!Zq(`R!3UphSATeL9wfOY z-;=AJR(@uiIr7NAZsa|pZ(3zg>pH8Ttm91u;2sHFD6be*=j$PM5^u&XRRJyd zz)$qO+;nk6_+`I=?p$PpIiao3zjjpJHBoVb=93D9z-=eWq8~`fzq_j>gDo^P=QJ$2 z!+Vr04?V4XCp&j)!W$BE)xUh?k+#WhkB#sC=RXLtd4TUm;JgiVHQqTip~C&n>5)P$ zM5w9)Ux&V8i;_V>dT(|r-P(r~jgCP%<`y5JPAguF%Sn##yNb&FIzgiqt+cr5ufU#P z(dPM0+y?lhCK6A+hZtwZ<&~efrYA3;teA1l1oLFF>Gd~7j{~B)j_RL%G?fO(OSTK^P3NtRg+2nXrXJ>TwBC~wChs%_5+PtC7$NXVpRCy0A(;UldGv_04Uew)=a!YGEnG_3s zO?05DB_OpKPI9Zj+XV3pUaVM@cJa(L4Rw4k3Fi?wX~Kj8CGs~TYqFE=#Km}XtLGzL z^j2uj8Xt50_V$|+wGART^QEJ5Fa6QO03{FG;|xv*Ej6ME^H6F2+!PPswEu22@qLSg z)f+u4-OD?T#RaQy?S#s>2wHQ4Q?w!K?CRl(sw31}h+{+fokJcDj4PraO$+t_Z?$_O zYGh49h`mRs_NxY`f7#siA5ODG26AoQ?J=fYF0&S<3I)Y*?U2hL(BLXFh**U=OFGX7 zpW!w9=L@O>GZMgCx0{iEl-`3>Hi3Vg-_CB?0o>E% zg;L>3C9$1Ddv*9s6>FkXr5t;l3&)yi zWDNt@Qh12*#_+@gC!SkO($@jEc=3KjwA(^ZL?mczzhb9!t+@5s;muXep%4WKbq>5a zAYBGY7P&8Wd{MYuN(oK)(n}YQ6n)6qgCZwcSON8EWRoW1%K@FdE@eiH+cJZ#w z+wB!jqM_NIMks|?SjX#0VxeI%?Y;BGWez81X{h~s0kYML(yW&0iY#6Z>=)7RF)=Zz zrg8SNu9J2(N_{*;^(^1z_G2R099!3Olsv(k<>tAmbG17DbF!``3?*XZ_)wK3xuN7X z=ek=z(&lT{zN|be+tDSh{v?4eQ@HBgU{!(rD6xGiDk?D^$>w zM<}%4IUetZ6{#~7Ypl9-h-woOKtsZkv*$dw_xGkI;d|_tXERk>onoCRNfy{;^*sur zyzrSDZ#pWc16_`eA2XlmePO=X^0cYcJYDly!!7+kNpgh}D#aSiMAmYk#W!)%yVIty z6=iTSZGRpF7bD<$_9S6Cv;4)6%ik&}xKg|3)B^mN=!sU#%1SfrN+9Fh!(!WarB;A>mmttOyV1W1lSHb|MWBMG%FRLk!7!#cU-MvU68@@u3i- zrw&qh#j(!HqT+U!TYFH$7m7tf{Pt51)rnB2XNwni*o=BmfYulY_Inm2SxZhVYH_u% z8Mz&z@LxSI|AnLVZ#KQtFD~C+c>f#A*7kYrXXWmwZx|`^tPHT{E}8uyk$9gb9=!T{ z%%s@50q4mneKH;7J_r2x;d8Q;EL-&Wo)&Vv=hJh~_>`OW685g=-_)ekVyzb@$A^ZE z+9hBaU14(+Rt{r`G?-a$|Dor*(PUXeUR;?md~^{9b-o6aIEMSVYKfY0NJHN5= z?caX&o@l+~0{Qs}T0W`sPHgfp z`xO6Emk*_1rpn9uyxTC^i*OUSo?t^xWR1qvZuSjT?HKOecJlT_)!0f_EB3B9cWaBPz6#vO!Q79^SaPcCgMDA2@7v1>wPGpBQtFa-BXWC%qFXJ0^bM4;#1ut@sR z#QQsIWQnYNa+08KxJn30CtAi4NKn!}R3h|fVkPq4K2pqhuOOd|c_rGhVQv|zHKTj2 zD%E&by$7qke|lj^4iBAYzMCFeWuG0NJ0}KwkDSu8a{cX6WPx_RuLiYJ2UKcu_3DZD zzm?12WKA?vzuJ|~@PwcaBSD*A(*6IM|9?|Q|7}n4x26jQERmSkT{6JihAO5X2-B>i zHsLd09O2_$pe_cokjqa;$_q8}fzZ6sRiagW=uWgR{3#cFy`+wx9;Zbi>v%MO$JU@KCmN;#EJ6hX2uy=k=aKecq44&1m4;=J@ zl#kZZy~Pryja{MZnFqfj#~apT38_=3QTP)bE{qs{+LJ3kSqjahF{a-qe_LYf$Gc6V9|CIxLW#!w-|t5s$pxKhD)( zGK+-pg;1((#WTWxU$Y;MS$Ljvs<7pUp8vqA3W$vTgTF!VN zWfku6+W1-LMT#9mhz@*>=ehhU7FE84NBcd zSbXH=vgx+-yo%|jz#oC(e)^?y14jVBL*n1ZCjUY?!*+qVrR0rHxXSLW%dq-m$KB2j zyBZDD&KZxLgcT~(nQZDX4^Y_AVYeR< z-JRpAk>V`3$07b@>X%tm6=k2tM$jtbGu}_jnWxbmWH14{>D0iXBrQFwZI^Xz8RzEs z#ODikd*s@O~kTj!waq;qH&@0?JD{DHG!B^Ce zm?e^5HlD41p5toWAO8xU$FUo=h^!$Ai#}s;^2AKFdD&t5L*2y9(2au@=7mR$tpMrS9c8YDT{CankKeeF9tQ+(pmCn;%(! zqKw)bCIqIvpYO4!4JUBunV?V$X`34gE;I`HEVo5&Zg%&yiQKns(;03&ahkG3{xn~ zzB4%d{&`^o{mrT8F{v){5uj^9 zsCp9wV}81g(>@Q*Lc{bZTARtMs{wKdm~n8^PX$vWdoFje$LRMe$RO5IbvNc_>P7wg z63Jf(e6G;ytCl0FLj_Lx+_5Q`D@kpe(Zm~;^>McZ#qMPDWGm7ob2hXJVejF}K7y`{ z^(%&(MfDeTB?e;x#!nA*bjE9HbHg-H!7Y+m`h(MsDw)s4Bf_i+VPs2|LdU*DVvmn3|4*qC!j5J6WRBh|(&n#TlNj9CkbAzclE!{5NU>tC;a+ z#pSXDa{@)L-mY!KPoi?=4y0jmp7}xQp5uL!)R1vO+9%S78Xp}|5H(EP`6HZ>)jmO+ zc>UCNaSmzA>@b}cr;%7A>*3};gh>g*^E&h2~w1IvV#)`Z1NWcJ>s_0%~?Q5O1 zT&LC+v_7&{D5C#<`dFmPBrSck#o~K$!qQMtU91t)aD3zjtSW2Vx`Gh*eiywB7R?mY zaU7C&yC;X*ZaR99m3?O;+gYU;Y5Az3cL67I?;mWTt>&^x?K0Fc25Pb=wtHsIW<62u zB+kc1w8>*)4cnhHO3L3?d-zfeh8Dhsy{(OnXxrj8_G{XA zGs?XhF8N=ycEhX&KTcOX*eS@ zw3|IwNp`m`W27y22N}!W>9(DJEAE<+{A>J8+i6Y$>LaP}+FLp(*@Dfv=4Q0um|l!y zW~A2Pzs;2@;IZ`lwD38!(r0bmg_nk|-{u_C$39eCs%63KT0EY+d|Q{e8h4ZZVBJoX zRa1;SZ8R~o+)#DR9e=DN@eA$vh(YGu`w_7=3`uyRayN~?3*pa|8GkX}kDE>2ph@ge zE;q^=3oy#WuMc%geKsPoAyg(P-f!;K2`py#Ep=Y>x+C00ufDX4HGZu9bq0FPx?D@^ zhNJ1s>46Dk+=7jqrnL(er5$BkSnM={&n}e{^=(llZwI*@B~_;?*==~a^X`}478s>$ z?vmzU>PfbEl5-u&)#Dj{Fv~(Lxz9yzB!n9fV0$Lke5K{43~9_smXy6OJ2%X{S@Az$*1K-tmZ4 z(2WfWanw8@Jm7EcV>N!nsSADjp|9ddAzlmA;98^MU!t>&{*=!5q6*N&0+k=?_veWYjd= z@1uM~&9Wn%Kc9P(fgc~Tu`Fw!;v?Yof&^_3fFS5!1y%lE8~abwJC$6LCOY~2{N%y>a(8f~Uw<&C z*v+3Js4NA1zfikyaRllGj+ZV#FX%qr?MfxN;RO3$b-`7AyBY#pf{F0iAPR7=oS%M2*{RO`w(B_8Xrrw&bslGdhzM_yz}o59 z%wb{AAjyKt(h9@gSd1F=I=ly!7rC0hImr3l=v7alMjRS8+?gtDfoXZa0sRHNgF}D- zMON3!0FqY!dU)Q+b+s~Xu>7|+_V3hHKURCZ_?I2#io^Pv#AsVUwJ3G*v?lwW7w!Ie z{B6@axpFM&auakKE;fL}xXbcvO;=$jm1<6pC*HQ(I7^cApM&S@Y zm{L_tY0ALA5=*$YBwrU5UnZCq2uqQay4b*c9Qd|$L@vsY)%5Fe-P*fl9hO%I{7iS; zs5UxnF2ibhh~(r}TP|JpgAsXkg_vW}^27@TJ(1@lglXE6w?C7Ww#N zm*qjyM~UT3=S7{6^CS4rf~cFu^3@I>nJHxhNjPND1F z@|e+DO4#;rzbzSmYRKnuYwk^yeGiBx?!^}=GZ4lbu=4xnK?Ih?+J?9T@qUt2Q$dTe znL?}~L0BZ|E_*2pKQh76fcDBcNu4E^V?-Yw%RxWO8g-H$6*P`{U^p0+?KHkn@XYt) z<|h6rx|tTqfcJo0e=uA==B^+xTb`>2hJsO-|8J=Gzh`Pdrq(-Z*6MA|1I$%6v|HB7 zuBf#NLbWvfh#QAtzzGwu0hkzw5l zC)SVl{S%x~)pN)~^cIuc7qPn2cR5ylxMz0&=Y_iWL$UgF|aSbc@I_}6t z-AL|;E95&3ifbd;7#D01scmWr<5hggyXDq3MW*(St&zZl5 z*P3u$uTkOd^BtWywT@^|ID`JukWJ%1DDF5%GlT<%JeMMZJ0v%xQD!TCJ^5WioR3h?Zc9T$-&y|w)0%m=fR)w zb@-WBibsw*io==hVTBN+#q!Csq_d>vERV#TjMaP`G5go&PbwXaZ-mF_JHOFRsTx4W?c^0iL)2zKZ+QZA$ zq;JUwrGA(y`iV(o2v(Te?b%X=6_wWV_NnO&C3&UgB<-&zo9SS3RlB%6$a@cpj_@OT3bADHlHoIlyB{)&6`kG2!pOS^!Fagc@6RUeOS2Tr(xWBk(` z$=1PnbOY38z(gq;;$X9NO;?%K=5Mj~&CrC{eIKao>YR-n=QmdjE9|->Cl|yxJsoMcVdTe{0{{OhP63O(QhO&SZ;17GAC|1 zCg5p1w}`!eKxXccF;r;cd%yC_>ha3-q;Dwe5kV)7!2_`@!OH`c+W==%3ie*5r}=S} z)?_*J!IQ0FM;(N;roUK4`5iN3mDcUJTPlzJCb4iXH#4JyNkN{lZ%?-Kt`oqz6)a2S znr6t$;*l?QGEe&Lj`2E4<6g63)Lzp5LYEcrI1FA%HgcA`J)AJcJCniBIVfpq7etI$I#rRocNwBQ#kWqAw&4=w$uis_U+9W0)J-kb_SGrRW+i--2lyH(N}c%d$QJQve}&NFiSbFg z5zOrXRjAowI*F9qJ5tv51}}w-q%p*;=i<31UT9XHo~g~7`3f~i z>c6QqApB7g2oXQ#9UWEv%iAQC?^+c+D*j@sT=OTAR%`sCox1}qhQ+Be74R6GXClo< zkw*%&u0Vu2MlGh5m`HDUG_3z!chsy*yC87n&1nZ}jJw;e%j$PbGoA>ug+@xRjCPRn z@Os0!1-;Uh)~cqseW>AAY8`_6YBLvN)OY~B^Rp0Rhh#PP^JGDf0@pgt>Apq0m_}=s zTSK^tk{b}+R-&izGBSSOP>`j9c!n6^^mw%-d)fF|L7C7>Vo5=jG>1+rpwXK2H0I(* zlDqO_)RsMnGbKYifjvDx^sF++j%iRYIuJjO|L{hqLYOo$W7oU()Z3K=esk5!eD0Tr zh-}l}k4F}0i{QgDt%`8kh`+^3n9D_Ecw9fDb{nwA@SwPs?3JlR9qk7;X4?Uu&bNA2 z-TbAR1C?N`2&zO+7gur<#eZq$xkvhM;)8$Jc;EgDKv6vIBoO`Lp+J$@T23u?C%K@EW zbDh@|FeR`za5S{gXFl?Jv0dspSId2XkB+yveDAsQj}_`e*|hMQTyNuWJB6%C@tp4%PL{2 zxn`edn=DE`)-YlGH?2C%f3TK_Naj51aX>lSX=?C!KAS=NS($+}c)8flA`WR?=*c=3 zF*ie(uMGyy-B3S3Ez?YkSfe#PCj=GBB2UpS@pnA0=TBSC#ts>~EwWTZ|JaK>h>bz1 zxi>Hh#W-@IJCXABwvW1c%DjYQmDB6775A50D9Mb!hpbKd&fBLxyuY0g2 zGP~A~$%(q5$RN`TJWRa|*AX4T2`A~2-o;toirNuwA8F>EHiC`qZ2Qm=@y^V`bN;L? z@iB}8*vuM6xcJj_M#I!Poc2GHP8{fQ-^ZYT3|^|g5boFKmt3(Iv|V=5#OFh}wMbY1 z8pVKMwX)}rP)$x8uIK1zmKjH6i^VM)Sqme|$15c#i^co%$GaGZsK3@8szeDaSSY6n zV~1bSZanif-<%y76=Yu>QeJZY`fs?!JIY; zM)v3j(%d7%E_fE=78-9&cDO5LZp^NJ6b&1Id+j^GvQSPJ-gr}>_NJ{a(*-z^&#xOq z|8!xZGkZO?2)$j9RDi5ZiO34QS=I29kNjxnQ?s_S>0E&T-MK+!J}LFcIDKYOR^9i> zTLf!(jXk@hm@#aDlCqwap}G2scG&i=9P2X7O1r7=syHI)vc2(-y&A1i_69KK%rz09 z1i>px4H($r9LB)!2zhr;YQ#Xh>X6>+2;sQT{=>TgcI=QeQx z#aql3@$rk%WfKaYBub?A=B;XDmW<1F6PCyi`g5>K=1NP(lWr;33n^>lU$ph+{Uvx%zNAG9?6{dtbpUHi8ze){XJ+o4f@s|Kom39{wAi!YJ;eawFw z#g`DpY}s)&<+#W0%QHQ56BDOiw4jLgtw?d#Z<1e1_xP;?Aw?oV(SbI(VM=>?%F=dp zKUB0V3eUSCp2Yi_D^@-$Gf`8CnC2Qn3lM^iTW73cR=grMk`RoV1vw^wT?gBuuS=yHG`HuJ< z@ClDaoNnPQH$8Zz?dLyLgTW%1OZ%ygU!TR_>#@1*zsKq!|C{qAcT%$A;+=G_#X1F4 zSP6hdzA3h!_j+tjB1WRM@AtXj+^f1*#3Uxa41O@p{yuW&a&;f%J}R>2T>E?CKIg4= zr$C7G$0j-XkBeviqm8Xh-1O(1?+>HPOSZ1%Ix~N#i87TNTUo1rR<1au*=jzd04AT5 zyTt+E4Hztt12ZyF#$3~Fn@P;b%?ig+q0Ns;{~I4qK89dwKR=|}k5p~dsh#g554{*~ zb5*NeaXAbE--pD_T1yf8czGliByvE7-DXkd-c5AAS>>n>p{{WUTc2P4@Z~|(X_^Zr zAJRR;ANLYa*vvx7BfsRhS~(XiXNml6!uR!cH3UfKYswkkh-3IiztVYe7Y{HUrbez= z&75wwG7x3y>FR@>zm)@v{947W!(EXZL25qZ)5H8$+Jz0~0sGfK`zW-Aoj)Twvz0rp z0w)1#4WvHNg&{7F0Fyz@<`Jd)=C4LG&n@i(_bKFWEx4#hqM6o!LtJ-Xif?TOACjd4 z-x;J<(6zG0gZX9?2_6i*^xsE5D4x;zmLcJyxzq)EU4e-p^wGvA7Ybv>!E$Tj-#N$%aMfs}6wUn%tB2SV(@T0qTB^(BDf0H6P-YiAor?oZ>*HezNUUTEeC zpS=B-X09a34)|OsVB6gl`ct+*!BZZr>@a(|dC&4Nbv&>0%oOml+5A?eF zJFdgR{m`HwrlK3CBOwcd;B(RaDijEnBi_pMR$^O@h;IERz4!j}hqqodGYVv~vC1MU zTO`O&4Yq<*^os9k?finGRaQbU#uODty`lY^60r$JfU{cbgx-}pjO15a!LIbQ4<7VY_{z~q zgKhP)@u#~B)JU3OqWkR!Gu2kEnJv+BpXOkQ;9uQU{E16y&&k*l{^NH z^*G?xbZwz^6ds)T*B*;)Il9|U@blh`G`M)eM3C-W7j8@CY zT#TT>SgmCtQ8j&_D&k5MGNjnXSDml5wxVVJ;`n1zgrlJa^Ug=lz?( zYxqkg543+&@~<-nFN5Jd-gHdV&EGoGWMz-|!%B2-N}#uOHHB~)+~z%>)VAj|WQUkZ zY7A^_cZqO`Sd|BZ;cq|K?6__$E*9kGm(EY;ZY}fNw#m2R8Jd>`(W!%K`~R7~@Ta-{ z?WO&peS5`6Huqf(R6_H^)H3KCc-Fu2g!aMV(L|p@&_9P9GKbx+5Y8|bIOz@k25ScX z8#Uv!O*`JA`|ron@;6UgwEj`B2;qb>)MEo`s4gN4Jr|Gw{__}c< 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 @@ + + + + + +