From f8cebcf1da0254a5211f73a95e131020da25174f Mon Sep 17 00:00:00 2001 From: Antony Male Date: Sun, 8 Mar 2015 11:29:05 +0000 Subject: [PATCH 01/22] Add 'Known Issues' to the README --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 806c67da..5491d952 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,22 @@ Grab the latest standalone .zip from the [releases](https://github.com/canton7/S Unzip, and run `SyncTrayzor.exe`. If you're updating, you'll need to copy the `data` folder across from your previous standalone installation. +Known Issues +------------ + +There are a couple of knows issues with SyncTrayzor. Fixes are on the way, but until then here's a head-up: + + - SyncTrayzor can't currently handle the HTTP Basic Authentication which Syncthing uses to password-protect the GUI. + The symptoms are the UI not loading, or showing 'Not Authorized' (if it's not loading, go to Syncthing -> Refresh Browser and 'Not Authorized' should appear). + The short-term fix is to either remove the password-protection on the GUI, or use an external browser (go to Syncthing -> Open External Browser). + See [Issue #13](https://github.com/canton7/SyncTrayzor/issues/13). + + - SyncTrayzor executes Syncthing from AppData, which some anti-malware tools won't allow. The symtom is an exception with the message + "System.ComponentModel.Win32Exception (0x80004005): This program is blocked by group policy. For more information, contact your system administrator" when trying to + start Syncthing. Better error reporting is on its way, but please whitelist `C:\Users\\AppData\Roaming\SyncTrayzor\syncthing.exe` (if you used the installer, + otherwise whereever your portable installation is) in your anti-malware program's settings. See [Issue #12](https://github.com/canton7/SyncTrayzor/issues/12). + + What will SyncTrayzor do to Syncthing? -------------------------------------- From f67a4be88740f4ddb898554ad577bce23467bd8f Mon Sep 17 00:00:00 2001 From: Antony Male Date: Sun, 8 Mar 2015 12:15:55 +0000 Subject: [PATCH 02/22] Set API-Key in Web request headers to avoid HTTP authentication Fixes #13 --- src/SyncTrayzor/Pages/ViewerViewModel.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/SyncTrayzor/Pages/ViewerViewModel.cs b/src/SyncTrayzor/Pages/ViewerViewModel.cs index 5d5160bf..8f56cb5c 100644 --- a/src/SyncTrayzor/Pages/ViewerViewModel.cs +++ b/src/SyncTrayzor/Pages/ViewerViewModel.cs @@ -95,6 +95,13 @@ bool IRequestHandler.OnBeforeResourceLoad(IWebBrowser browser, IRequest request, Process.Start(request.Url); return true; } + + // See https://github.com/canton7/SyncTrayzor/issues/13 + // and https://github.com/cefsharp/CefSharp/issues/534#issuecomment-60694502 + var headers = request.Headers; + headers["X-API-Key"] += this.syncThingManager.ApiKey; + request.Headers = headers; + return false; } From 460a222e4521f6cacb4c8a643ecce2dc7c6f066e Mon Sep 17 00:00:00 2001 From: Antony Male Date: Mon, 9 Mar 2015 12:27:27 +0000 Subject: [PATCH 03/22] Catch Win32Exception with code 0x80004005 Fixes #12 --- .../NotifyIcon/NotifyIconViewModel.cs | 3 +- src/SyncTrayzor/Pages/ShellViewModel.cs | 2 +- .../Utils/SafeSyncthingExtensions.cs | 35 +++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/SyncTrayzor/Utils/SafeSyncthingExtensions.cs diff --git a/src/SyncTrayzor/NotifyIcon/NotifyIconViewModel.cs b/src/SyncTrayzor/NotifyIcon/NotifyIconViewModel.cs index 9a44a0c0..3a2ca045 100644 --- a/src/SyncTrayzor/NotifyIcon/NotifyIconViewModel.cs +++ b/src/SyncTrayzor/NotifyIcon/NotifyIconViewModel.cs @@ -1,6 +1,7 @@ using Stylet; using SyncTrayzor.Pages; using SyncTrayzor.SyncThing; +using SyncTrayzor.Utils; using System; using System.Collections.Generic; using System.Linq; @@ -82,7 +83,7 @@ public bool CanStart } public void Start() { - this.syncThingManager.Start(); + this.syncThingManager.StartWithErrorDialog(this.windowManager); } public bool CanStop diff --git a/src/SyncTrayzor/Pages/ShellViewModel.cs b/src/SyncTrayzor/Pages/ShellViewModel.cs index 9ecdf05e..58129c6d 100644 --- a/src/SyncTrayzor/Pages/ShellViewModel.cs +++ b/src/SyncTrayzor/Pages/ShellViewModel.cs @@ -58,7 +58,7 @@ public bool CanStart } public void Start() { - this.syncThingManager.Start(); + this.syncThingManager.StartWithErrorDialog(this.windowManager); } public bool CanStop diff --git a/src/SyncTrayzor/Utils/SafeSyncthingExtensions.cs b/src/SyncTrayzor/Utils/SafeSyncthingExtensions.cs new file mode 100644 index 00000000..b55f119c --- /dev/null +++ b/src/SyncTrayzor/Utils/SafeSyncthingExtensions.cs @@ -0,0 +1,35 @@ +using Stylet; +using SyncTrayzor.SyncThing; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace SyncTrayzor.Utils +{ + public static class SafeSyncthingExtensions + { + public static void StartWithErrorDialog(this ISyncThingManager syncThingManager, IWindowManager windowManager) + { + try + { + syncThingManager.Start(); + } + catch (Win32Exception e) + { + if (e.ErrorCode != -2147467259) + throw; + + // Possibly "This program is blocked by group policy. For more information, contact your system administrator" caused + // by e.g. CryptoLocker? + var msg = String.Format("Unable to start Syncthing: {0}\n\nThis could be because Windows if set up to forbid executing files " + + "in AppData, or because you have anti-malware installed (e.g. CryptoPrevent ) which prevents executing files in AppData.\n\n" + + "Please adjust your settings / whitelists to allow '{1}' to execute", e.Message, syncThingManager.ExecutablePath); + windowManager.ShowMessageBox(msg, "Error starting Syncthing", MessageBoxButton.OK, icon: MessageBoxImage.Error); + } + } + } +} From 5d308c43e2a7b559bf1c947c383068bc8c11f2fa Mon Sep 17 00:00:00 2001 From: Antony Male Date: Mon, 9 Mar 2015 13:28:10 +0000 Subject: [PATCH 04/22] WIP: Ignore 'synced' events after device connection/disconnection --- .../NotifyIcon/NotifyIconManager.cs | 4 +-- .../SyncThing/Api/DeviceConnectedEvent.cs | 34 +++++++++++++++++++ .../SyncThing/Api/DeviceDisconnectedEvent.cs | 34 +++++++++++++++++++ .../SyncThing/Api/EventConverter.cs | 4 ++- .../SyncThing/Api/IEventVisitor.cs | 2 ++ .../SyncThing/SyncThingEventWatcher.cs | 28 +++++++++++++++ src/SyncTrayzor/SyncThing/SyncThingManager.cs | 20 +++++++++-- src/SyncTrayzor/SyncTrayzor.csproj | 3 ++ 8 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 src/SyncTrayzor/SyncThing/Api/DeviceConnectedEvent.cs create mode 100644 src/SyncTrayzor/SyncThing/Api/DeviceDisconnectedEvent.cs diff --git a/src/SyncTrayzor/NotifyIcon/NotifyIconManager.cs b/src/SyncTrayzor/NotifyIcon/NotifyIconManager.cs index a419c513..027bf304 100644 --- a/src/SyncTrayzor/NotifyIcon/NotifyIconManager.cs +++ b/src/SyncTrayzor/NotifyIcon/NotifyIconManager.cs @@ -75,8 +75,8 @@ public NotifyIconManager( this.syncThingManager.FolderSyncStateChanged += (o, e) => { - if (this.ShowSynchronizedBalloon && this.syncThingManager.StartedAt.HasValue && - DateTime.UtcNow - this.syncThingManager.StartedAt.Value > TimeSpan.FromSeconds(60) && + if (this.ShowSynchronizedBalloon && + DateTime.UtcNow - this.syncThingManager.LastConnectivityEventTime > TimeSpan.FromSeconds(5) && e.SyncState == FolderSyncState.Idle && e.PrevSyncState == FolderSyncState.Syncing) { Application.Current.Dispatcher.CheckAccess(); // Double-check diff --git a/src/SyncTrayzor/SyncThing/Api/DeviceConnectedEvent.cs b/src/SyncTrayzor/SyncThing/Api/DeviceConnectedEvent.cs new file mode 100644 index 00000000..3bebdd9d --- /dev/null +++ b/src/SyncTrayzor/SyncThing/Api/DeviceConnectedEvent.cs @@ -0,0 +1,34 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SyncTrayzor.SyncThing.Api +{ + public class DeviceConnectedEventData + { + [JsonProperty("addr")] + public string Address { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + } + + public class DeviceConnectedEvent : Event + { + [JsonProperty("data")] + public DeviceConnectedEventData Data { get; set; } + + public override void Visit(IEventVisitor visitor) + { + visitor.Accept(this); + } + + public override string ToString() + { + return String.Format("", this.Id, this.Time, this.Data.Address, this.Data.Id); + } + } +} diff --git a/src/SyncTrayzor/SyncThing/Api/DeviceDisconnectedEvent.cs b/src/SyncTrayzor/SyncThing/Api/DeviceDisconnectedEvent.cs new file mode 100644 index 00000000..4557470f --- /dev/null +++ b/src/SyncTrayzor/SyncThing/Api/DeviceDisconnectedEvent.cs @@ -0,0 +1,34 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SyncTrayzor.SyncThing.Api +{ + public class DeviceDisconnectedEventData + { + [JsonProperty("error")] + public string Error { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + } + + public class DeviceDisconnectedEvent : Event + { + [JsonProperty("data")] + public DeviceDisconnectedEventData Data { get; set; } + + public override void Visit(IEventVisitor visitor) + { + visitor.Accept(this); + } + + public override string ToString() + { + return String.Format("", this.Id, this.Time, this.Data.Error, this.Data.Id); + } + } +} diff --git a/src/SyncTrayzor/SyncThing/Api/EventConverter.cs b/src/SyncTrayzor/SyncThing/Api/EventConverter.cs index e897aa0c..a8cec4ab 100644 --- a/src/SyncTrayzor/SyncThing/Api/EventConverter.cs +++ b/src/SyncTrayzor/SyncThing/Api/EventConverter.cs @@ -17,7 +17,9 @@ public class EventConverter : JsonCreationConverter { EventType.ItemStarted, typeof(ItemStartedEvent) }, { EventType.ItemFinished, typeof(ItemFinishedEvent) }, { EventType.StateChanged, typeof(StateChangedEvent) }, - { EventType.StartupComplete, typeof(StartupCompleteEvent) } + { EventType.StartupComplete, typeof(StartupCompleteEvent) }, + { EventType.DeviceConnected, typeof(DeviceConnectedEvent) }, + { EventType.DeviceDisconnected, typeof(DeviceDisconnectedEvent) }, }; protected override Event Create(Type objectType, JObject jObject) diff --git a/src/SyncTrayzor/SyncThing/Api/IEventVisitor.cs b/src/SyncTrayzor/SyncThing/Api/IEventVisitor.cs index dc487324..ca89c55f 100644 --- a/src/SyncTrayzor/SyncThing/Api/IEventVisitor.cs +++ b/src/SyncTrayzor/SyncThing/Api/IEventVisitor.cs @@ -15,5 +15,7 @@ public interface IEventVisitor void Accept(ItemStartedEvent evt); void Accept(ItemFinishedEvent evt); void Accept(StartupCompleteEvent evt); + void Accept(DeviceConnectedEvent evt); + void Accept(DeviceDisconnectedEvent evt); } } diff --git a/src/SyncTrayzor/SyncThing/SyncThingEventWatcher.cs b/src/SyncTrayzor/SyncThing/SyncThingEventWatcher.cs index e1cf6403..602749de 100644 --- a/src/SyncTrayzor/SyncThing/SyncThingEventWatcher.cs +++ b/src/SyncTrayzor/SyncThing/SyncThingEventWatcher.cs @@ -28,6 +28,8 @@ public interface ISyncThingEventWatcher : ISyncThingPoller event EventHandler StartupComplete; event EventHandler ItemStarted; event EventHandler ItemFinished; + event EventHandler DeviceConnected; + event EventHandler DeviceDisconnected; } public class SyncThingEventWatcher : SyncThingPoller, ISyncThingEventWatcher, IEventVisitor @@ -41,6 +43,8 @@ public class SyncThingEventWatcher : SyncThingPoller, ISyncThingEventWatcher, IE public event EventHandler StartupComplete; public event EventHandler ItemStarted; public event EventHandler ItemFinished; + public event EventHandler DeviceConnected; + public event EventHandler DeviceDisconnected; public SyncThingEventWatcher(ISyncThingApiClient apiClient) : base(TimeSpan.Zero) @@ -109,6 +113,20 @@ private void OnItemFinished(string folder, string item) handler(this, new ItemStateChangedEventArgs(folder, item)); } + private void OnDeviceConnected() + { + var handler = this.DeviceConnected; + if (handler != null) + handler(this, EventArgs.Empty); + } + + private void OnDeviceDisconnected() + { + var handler = this.DeviceDisconnected; + if (handler != null) + handler(this, EventArgs.Empty); + } + #region IEventVisitor public void Accept(GenericEvent evt) @@ -145,6 +163,16 @@ public void Accept(StartupCompleteEvent evt) this.OnStartupComplete(); } + public void Accept(DeviceConnectedEvent evt) + { + this.OnDeviceConnected(); + } + + public void Accept(DeviceDisconnectedEvent evt) + { + this.OnDeviceDisconnected(); + } + #endregion } } diff --git a/src/SyncTrayzor/SyncThing/SyncThingManager.cs b/src/SyncTrayzor/SyncThing/SyncThingManager.cs index 48c941db..5b07c57d 100644 --- a/src/SyncTrayzor/SyncThing/SyncThingManager.cs +++ b/src/SyncTrayzor/SyncThing/SyncThingManager.cs @@ -29,7 +29,7 @@ public interface ISyncThingManager : IDisposable Uri Address { get; set; } string SyncthingTraceFacilities { get; set; } string SyncthingCustomHomeDir { get; set; } - DateTime? StartedAt { get; } + DateTime LastConnectivityEventTime { get; } SyncthingVersion Version { get; } void Start(); @@ -54,7 +54,13 @@ public class SyncThingManager : ISyncThingManager private readonly ISyncThingEventWatcher eventWatcher; private readonly ISyncThingConnectionsWatcher connectionsWatcher; - public DateTime? StartedAt { get; private set; } + private DateTime _lastConnectivityEventTime; + private readonly object lastConnectivityEventTimeLock = new object(); + public DateTime LastConnectivityEventTime + { + get { lock (this.lastConnectivityEventTimeLock) { return this._lastConnectivityEventTime; } } + private set { lock (this.lastConnectivityEventTimeLock) { this._lastConnectivityEventTime = value; } } + } private readonly object stateLock = new object(); public SyncThingState State { get; private set; } @@ -103,6 +109,7 @@ public SyncThingManager( ISyncThingConnectionsWatcher connectionsWatcher) { this.folders = new ConcurrentDictionary(); + this.LastConnectivityEventTime = DateTime.UtcNow; this.eventDispatcher = new SynchronizedEventDispatcher(this); this.processRunner = processRunner; @@ -117,6 +124,8 @@ public SyncThingManager( this.eventWatcher.SyncStateChanged += (o, e) => this.OnSyncStateChanged(e); this.eventWatcher.ItemStarted += (o, e) => this.ItemStarted(e.Folder, e.Item); this.eventWatcher.ItemFinished += (o, e) => this.ItemFinished(e.Folder, e.Item); + this.eventWatcher.DeviceConnected += (o, e) => this.DeviceConnectedOrDisconnected(); + this.eventWatcher.DeviceDisconnected += (o, e) => this.DeviceConnectedOrDisconnected(); this.connectionsWatcher.TotalConnectionStatsChanged += (o, e) => this.OnTotalConnectionStatsChanged(e.TotalConnectionStats); } @@ -223,7 +232,7 @@ private void ProcessStopped(SyncThingExitStatus exitStatus) private async Task StartupCompleteAsync() { - this.StartedAt = DateTime.UtcNow; + this.LastConnectivityEventTime = DateTime.UtcNow; this.SetState(SyncThingState.Running); var configTask = this.apiClient.FetchConfigAsync(); @@ -269,6 +278,11 @@ private void ItemFinished(string folderId, string item) folder.RemoveSyncingPath(item); } + private void DeviceConnectedOrDisconnected() + { + this.LastConnectivityEventTime = DateTime.UtcNow; + } + private void OnMessageLogged(string logMessage) { this.eventDispatcher.Raise(this.MessageLogged, new MessageLoggedEventArgs(logMessage)); diff --git a/src/SyncTrayzor/SyncTrayzor.csproj b/src/SyncTrayzor/SyncTrayzor.csproj index 58d5c93d..feb38365 100644 --- a/src/SyncTrayzor/SyncTrayzor.csproj +++ b/src/SyncTrayzor/SyncTrayzor.csproj @@ -150,6 +150,8 @@ + + @@ -181,6 +183,7 @@ + From fe59dedb17956e9d8c1b15cca693922303823aa2 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Mon, 9 Mar 2015 16:03:31 +0000 Subject: [PATCH 05/22] Remove ShellViewModel.ExecutablePath, as it's no longer used --- src/SyncTrayzor/Pages/ShellViewModel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/SyncTrayzor/Pages/ShellViewModel.cs b/src/SyncTrayzor/Pages/ShellViewModel.cs index 58129c6d..2d8b568e 100644 --- a/src/SyncTrayzor/Pages/ShellViewModel.cs +++ b/src/SyncTrayzor/Pages/ShellViewModel.cs @@ -22,7 +22,6 @@ public class ShellViewModel : Screen, INotifyIconDelegate private readonly Func aboutViewModelFactory; public bool WindowActivated { get; set; } - public string ExecutablePath { get; private set; } public ConsoleViewModel Console { get; private set; } public ViewerViewModel Viewer { get; private set; } From c59ab2ef9ac2863f94a76246bb2d45da64765c93 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Mon, 9 Mar 2015 16:23:25 +0000 Subject: [PATCH 06/22] Remove Height on SettingsView Window It's Set to SizeToContent=Height anyway, so setting Heigh explicitly only servies to confuse the designer --- src/SyncTrayzor/Pages/SettingsView.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SyncTrayzor/Pages/SettingsView.xaml b/src/SyncTrayzor/Pages/SettingsView.xaml index 7344c3b2..eff3bf4c 100644 --- a/src/SyncTrayzor/Pages/SettingsView.xaml +++ b/src/SyncTrayzor/Pages/SettingsView.xaml @@ -7,7 +7,7 @@ xmlns:pages="clr-namespace:SyncTrayzor.Pages" mc:Ignorable="d" d:DataContext="{d:DesignInstance pages:SettingsViewModel}" - Height="600" Width="400" + Width="400" Title="Settings" ResizeMode="NoResize" SizeToContent="Height"> From 37bee282c2fc9aed34c137f95bc9d6d8017ab934 Mon Sep 17 00:00:00 2001 From: Adrian Rudnik Date: Mon, 9 Mar 2015 16:51:11 +0100 Subject: [PATCH 07/22] Add option to obfuscate device IDs in the console/log view --- src/SyncTrayzor/Pages/ConsoleViewModel.cs | 24 +++++++++++++++++++--- src/SyncTrayzor/Pages/SettingsView.xaml | 21 ++++++++++--------- src/SyncTrayzor/Pages/SettingsViewModel.cs | 7 ++++++- src/SyncTrayzor/Services/Configuration.cs | 9 +++++--- 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/SyncTrayzor/Pages/ConsoleViewModel.cs b/src/SyncTrayzor/Pages/ConsoleViewModel.cs index 89c9058a..56b04e86 100644 --- a/src/SyncTrayzor/Pages/ConsoleViewModel.cs +++ b/src/SyncTrayzor/Pages/ConsoleViewModel.cs @@ -1,11 +1,13 @@ -using Stylet; +using Stylet; using SyncTrayzor.SyncThing; using SyncTrayzor.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; +using SyncTrayzor.Services; namespace SyncTrayzor.Pages { @@ -13,18 +15,34 @@ public class ConsoleViewModel : Screen { private const int maxLogMessages = 1500; + // Leave just the first set of digits, removing everything after it + private static readonly Regex deviceIdObfuscationRegex = new Regex(@"-[0-9A-Z]{7}-[0-9A-Z]{7}-[0-9A-Z]{7}-[0-9A-Z]{7}-[0-9A-Z]{7}-[0-9A-Z]{7}-[0-9A-Z]{7}"); + private readonly ISyncThingManager syncThingManager; + private readonly IConfigurationProvider configurationProvider; public ObservableQueue LogMessages { get; private set; } - public ConsoleViewModel(ISyncThingManager syncThingManager) + public ConsoleViewModel( + ISyncThingManager syncThingManager, + IConfigurationProvider configurationProvider) { this.syncThingManager = syncThingManager; + this.configurationProvider = configurationProvider; this.LogMessages = new ObservableQueue(); + var configuration = this.configurationProvider.Load(); + this.configurationProvider.ConfigurationChanged += (o, e) => configuration = e.NewConfiguration; + this.syncThingManager.MessageLogged += (o, e) => { - this.LogMessages.Enqueue(e.LogMessage); + var message = e.LogMessage; + + // Check if device IDs need to be obfuscated + if (configuration.ObfuscateDeviceIDs) + message = deviceIdObfuscationRegex.Replace(message, ""); + + this.LogMessages.Enqueue(message); if (this.LogMessages.Count > maxLogMessages) this.LogMessages.Dequeue(); }; diff --git a/src/SyncTrayzor/Pages/SettingsView.xaml b/src/SyncTrayzor/Pages/SettingsView.xaml index eff3bf4c..261621a7 100644 --- a/src/SyncTrayzor/Pages/SettingsView.xaml +++ b/src/SyncTrayzor/Pages/SettingsView.xaml @@ -1,4 +1,4 @@ - - + - + Minimize to tray @@ -28,9 +28,10 @@ Show Tray Icon only on close Show 'Synchronized' balloon messages Alert if a new version of SyncTrayzor is available + Obfuscate device IDs in the log view - + @@ -43,7 +44,7 @@ you can set it to whatever value you like here. - + Start Syncthing when SyncTrayzor starts @@ -51,11 +52,11 @@ - + You must restart Syncthing for changes in these settings to take effect - + - Automatically start on login + Automatically start on login @@ -90,7 +91,7 @@ - + @@ -108,7 +109,7 @@ - + diff --git a/src/SyncTrayzor/Pages/SettingsViewModel.cs b/src/SyncTrayzor/Pages/SettingsViewModel.cs index fb9d0d5d..7c8934cd 100644 --- a/src/SyncTrayzor/Pages/SettingsViewModel.cs +++ b/src/SyncTrayzor/Pages/SettingsViewModel.cs @@ -1,4 +1,4 @@ -using Stylet; +using Stylet; using SyncTrayzor.Services; using SyncTrayzor.SyncThing; using System; @@ -25,6 +25,7 @@ public class SettingsViewModel : Screen public bool CloseToTray { get; set; } public bool ShowSynchronizedBalloon { get; set; } public bool NotifyOfNewVersions { get; set; } + public bool ObfuscateDeviceIDs { get; set; } public bool StartSyncThingAutomatically { get; set; } public string SyncThingAddress { get; set; } @@ -64,6 +65,8 @@ public SettingsViewModel(IConfigurationProvider configurationProvider, IAutostar this.CloseToTray = configuration.CloseToTray; this.ShowSynchronizedBalloon = configuration.ShowSynchronizedBalloon; this.NotifyOfNewVersions = configuration.NotifyOfNewVersions; + this.ObfuscateDeviceIDs = configuration.ObfuscateDeviceIDs; + this.StartSyncThingAutomatically = configuration.StartSyncthingAutomatically; this.SyncThingAddress = configuration.SyncthingAddress; this.SyncThingApiKey = configuration.SyncthingApiKey; @@ -95,6 +98,8 @@ public void Save() configuration.CloseToTray = this.CloseToTray; configuration.ShowSynchronizedBalloon = this.ShowSynchronizedBalloon; configuration.NotifyOfNewVersions = this.NotifyOfNewVersions; + configuration.ObfuscateDeviceIDs = this.ObfuscateDeviceIDs; + configuration.StartSyncthingAutomatically = this.StartSyncThingAutomatically; configuration.SyncthingAddress = this.SyncThingAddress; configuration.SyncthingApiKey = this.SyncThingApiKey; diff --git a/src/SyncTrayzor/Services/Configuration.cs b/src/SyncTrayzor/Services/Configuration.cs index 29bcd98c..9921bd93 100644 --- a/src/SyncTrayzor/Services/Configuration.cs +++ b/src/SyncTrayzor/Services/Configuration.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -50,6 +50,7 @@ public class Configuration [XmlArrayItem("Folder")] public List Folders { get; set; } public bool NotifyOfNewVersions { get; set; } + public bool ObfuscateDeviceIDs { get; set; } [XmlIgnore] public Version LatestNotifiedVersion { get; set; } @@ -77,6 +78,7 @@ public Configuration(string syncThingApiKey, bool isPortableMode) this.SyncthingUseCustomHome = isPortableMode; this.Folders = new List(); this.NotifyOfNewVersions = true; + this.ObfuscateDeviceIDs = true; this.LatestNotifiedVersion = null; } @@ -93,6 +95,7 @@ public Configuration(Configuration other) this.SyncthingUseCustomHome = other.SyncthingUseCustomHome; this.Folders = other.Folders.Select(x => new FolderConfiguration(x)).ToList(); this.NotifyOfNewVersions = other.NotifyOfNewVersions; + this.ObfuscateDeviceIDs = other.ObfuscateDeviceIDs; this.LatestNotifiedVersion = other.LatestNotifiedVersion; } @@ -100,10 +103,10 @@ public override string ToString() { return String.Format("", + "SyncthingUseCustomHome={8} Folders=[{9}] NotifyOfNewVersions={10} LastNotifiedVersion={11} ObfuscateDeviceIDs={12}>", this.ShowTrayIconOnlyOnClose, this.MinimizeToTray, this.CloseToTray, this.ShowSynchronizedBalloon, this.SyncthingAddress, this.StartSyncthingAutomatically, this.SyncthingApiKey, this.SyncthingTraceFacilities, this.SyncthingUseCustomHome, - String.Join(", ", this.Folders), this.NotifyOfNewVersions, this.LatestNotifiedVersion); + String.Join(", ", this.Folders), this.NotifyOfNewVersions, this.LatestNotifiedVersion, this.ObfuscateDeviceIDs); } } } From 9b4d00860e3aab59adedc0d123dd0ccdefc6ec06 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Mon, 9 Mar 2015 17:29:13 +0000 Subject: [PATCH 08/22] Add Adrial Rudnik to contributers --- Contributers.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Contributers.md b/Contributers.md index cd5c5d64..87a5bd42 100644 --- a/Contributers.md +++ b/Contributers.md @@ -5,3 +5,4 @@ The following individuals have contributed in same way towards making SyncTrayzo - [Joel Kåberg](https://github.com/jkaberg): SyncTrayzor's first user! Caught bugs I would never have spotted. - [d4k0](https://github.com/d4k0): Fixed some icon problems. + - [Adrian Rudnik](https://github.com/kreischweide): SyncTrayzor's first PR! Added device ID obfuscation From 0048d1c4720df1df2e07a03f7699bfc013a2b34a Mon Sep 17 00:00:00 2001 From: Antony Male Date: Mon, 9 Mar 2015 20:03:57 +0000 Subject: [PATCH 09/22] Be better at handling errors during shutdown - Don't try and show a dialog if the application is exiting - Flush the log after writing an error, in case we crash straight after This will help in debugging #15 --- src/SyncTrayzor/Bootstrapper.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/SyncTrayzor/Bootstrapper.cs b/src/SyncTrayzor/Bootstrapper.cs index 0221b146..83bdec5f 100644 --- a/src/SyncTrayzor/Bootstrapper.cs +++ b/src/SyncTrayzor/Bootstrapper.cs @@ -22,6 +22,8 @@ namespace SyncTrayzor { public class Bootstrapper : Bootstrapper { + private bool exiting; + protected override void ConfigureIoC(IStyletIoCBuilder builder) { builder.Bind().ToInstance(new ApplicationState(this.Application)); @@ -97,9 +99,16 @@ protected override void OnLaunch() protected override void OnUnhandledException(DispatcherUnhandledExceptionEventArgs e) { - var windowManager = this.Container.Get(); var logger = LogManager.GetCurrentClassLogger(); logger.Error("An unhandled exception occurred", e.Exception); + LogManager.Flush(); + + // If we're shutting down, we're not going to be able to display an error dialog.... + // We've logged it. Nothing else we can do. + if (this.exiting) + return; + + var windowManager = this.Container.Get(); var configurationException = e.Exception as ConfigurationException; if (configurationException != null) @@ -117,6 +126,8 @@ protected override void OnUnhandledException(DispatcherUnhandledExceptionEventAr protected override void OnExit(ExitEventArgs e) { + this.exiting = true; + // Try and be nice and close SyncTrayzor gracefully, before the Dispose call on SyncThingProcessRunning kills it dead this.Container.Get().StopAsync().Wait(500); } From a9abf6287e7be827dde3bf168716a795ecefc601 Mon Sep 17 00:00:00 2001 From: Adrian Rudnik Date: Mon, 9 Mar 2015 22:01:18 +0100 Subject: [PATCH 10/22] Chromium will now send the correct Accept-Language header This allows Syncthing to show the appropriate translation. Fixes #17 --- src/SyncTrayzor/Pages/ViewerViewModel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/SyncTrayzor/Pages/ViewerViewModel.cs b/src/SyncTrayzor/Pages/ViewerViewModel.cs index 8f56cb5c..40925c6e 100644 --- a/src/SyncTrayzor/Pages/ViewerViewModel.cs +++ b/src/SyncTrayzor/Pages/ViewerViewModel.cs @@ -1,10 +1,11 @@ -using Stylet; +using Stylet; using SyncTrayzor.SyncThing; using SyncTrayzor.Xaml; using SyncTrayzor.Utils; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -100,6 +101,7 @@ bool IRequestHandler.OnBeforeResourceLoad(IWebBrowser browser, IRequest request, // and https://github.com/cefsharp/CefSharp/issues/534#issuecomment-60694502 var headers = request.Headers; headers["X-API-Key"] += this.syncThingManager.ApiKey; + headers["Accept-Language"] = CultureInfo.CurrentCulture.Name + @";q=0.8," + CultureInfo.CurrentUICulture.Name + @";q=0.6,en;q=0.4"; request.Headers = headers; return false; From ced44fedfc6a631433804215b228e1a2ffe1df70 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Mon, 9 Mar 2015 20:38:57 +0000 Subject: [PATCH 11/22] Add validation to SettingsViewModel --- src/SyncTrayzor/Bootstrapper.cs | 5 +++ src/SyncTrayzor/Pages/SettingsView.xaml | 8 ++-- src/SyncTrayzor/Pages/SettingsViewModel.cs | 35 ++++++++++++++- .../Services/ConfigurationProvider.cs | 2 +- src/SyncTrayzor/SyncTrayzor.csproj | 4 ++ src/SyncTrayzor/Utils/FluentModelValidator.cs | 43 +++++++++++++++++++ src/SyncTrayzor/Xaml/Resources.xaml | 23 ++++++++++ src/SyncTrayzor/packages.config | 1 + 8 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 src/SyncTrayzor/Utils/FluentModelValidator.cs diff --git a/src/SyncTrayzor/Bootstrapper.cs b/src/SyncTrayzor/Bootstrapper.cs index 83bdec5f..a28b2648 100644 --- a/src/SyncTrayzor/Bootstrapper.cs +++ b/src/SyncTrayzor/Bootstrapper.cs @@ -1,4 +1,5 @@ using CefSharp; +using FluentValidation; using NLog; using Stylet; using StyletIoC; @@ -8,6 +9,7 @@ using SyncTrayzor.Services; using SyncTrayzor.Services.UpdateChecker; using SyncTrayzor.SyncThing; +using SyncTrayzor.Utils; using System; using System.Collections.Generic; using System.Diagnostics; @@ -39,6 +41,9 @@ protected override void ConfigureIoC(IStyletIoCBuilder builder) builder.Bind().To().InSingletonScope(); builder.Bind().To().InSingletonScope(); builder.Bind().To().InSingletonScope(); + + builder.Bind(typeof(IModelValidator<>)).To(typeof(FluentModelValidator<>)); + builder.Bind(typeof(IValidator<>)).ToAllImplementations(); } protected override void Configure() diff --git a/src/SyncTrayzor/Pages/SettingsView.xaml b/src/SyncTrayzor/Pages/SettingsView.xaml index 261621a7..746f6dba 100644 --- a/src/SyncTrayzor/Pages/SettingsView.xaml +++ b/src/SyncTrayzor/Pages/SettingsView.xaml @@ -19,6 +19,8 @@ + + + + \ No newline at end of file diff --git a/src/SyncTrayzor/packages.config b/src/SyncTrayzor/packages.config index e7173e46..0554c16a 100644 --- a/src/SyncTrayzor/packages.config +++ b/src/SyncTrayzor/packages.config @@ -4,6 +4,7 @@ + From ba541663041f8c41fa3e2c1a222b9258b9e846d3 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Tue, 10 Mar 2015 09:17:04 +0000 Subject: [PATCH 12/22] Add FluentValidation to the list of open source components --- .../Pages/ThirdPartyComponentsViewModel.cs | 9 + .../Resources/Licenses/FluentValidation.txt | 176 ++++++++++++++++++ src/SyncTrayzor/SyncTrayzor.csproj | 3 + 3 files changed, 188 insertions(+) create mode 100644 src/SyncTrayzor/Resources/Licenses/FluentValidation.txt diff --git a/src/SyncTrayzor/Pages/ThirdPartyComponentsViewModel.cs b/src/SyncTrayzor/Pages/ThirdPartyComponentsViewModel.cs index 1f6dc1ff..cba6019a 100644 --- a/src/SyncTrayzor/Pages/ThirdPartyComponentsViewModel.cs +++ b/src/SyncTrayzor/Pages/ThirdPartyComponentsViewModel.cs @@ -80,6 +80,15 @@ public ThirdPartyComponentsViewModel() LicenseText = this.LoadLicense("NotifyIcon.txt") }, new ThirdPartyComponent() + { + Name = "Fluent Validation", + Description = "A small validation library for .NET that uses a fluent interface and lambda expressions for building validation rules for your business objects", + Homepage = "https://fluentvalidation.codeplex.com", + License = "Apache License 2.0", + Notes = "Provides validation for user inputs", + LicenseText = this.LoadLicense("FluentValidation.txt") + }, + new ThirdPartyComponent() { Name = "PropertyChanged.Fody", Description = "Injects INotifyPropertyChanged code into properties at compile time", diff --git a/src/SyncTrayzor/Resources/Licenses/FluentValidation.txt b/src/SyncTrayzor/Resources/Licenses/FluentValidation.txt new file mode 100644 index 00000000..9e0824e5 --- /dev/null +++ b/src/SyncTrayzor/Resources/Licenses/FluentValidation.txt @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/src/SyncTrayzor/SyncTrayzor.csproj b/src/SyncTrayzor/SyncTrayzor.csproj index 22eb3320..6599d0b3 100644 --- a/src/SyncTrayzor/SyncTrayzor.csproj +++ b/src/SyncTrayzor/SyncTrayzor.csproj @@ -334,6 +334,9 @@ + + + From d37f5d96db7e2e5eb593dc022598736f8211cefc Mon Sep 17 00:00:00 2001 From: Antony Male Date: Tue, 10 Mar 2015 09:21:55 +0000 Subject: [PATCH 13/22] Ensure we handle exceptions while handling unhandled exceptions When an unhandled exception occurs, we try and be nice and display it. If an exception occurs while doing *that*, swallow (and log it). This can happen in some edge cases (it used to happen when shutting down, but that was fixed), and if we throw the derived exception we'll put the wrong stack trace into the event log. Helps with #15 --- src/SyncTrayzor/Bootstrapper.cs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/SyncTrayzor/Bootstrapper.cs b/src/SyncTrayzor/Bootstrapper.cs index a28b2648..fe7c6141 100644 --- a/src/SyncTrayzor/Bootstrapper.cs +++ b/src/SyncTrayzor/Bootstrapper.cs @@ -113,19 +113,29 @@ protected override void OnUnhandledException(DispatcherUnhandledExceptionEventAr if (this.exiting) return; - var windowManager = this.Container.Get(); - - var configurationException = e.Exception as ConfigurationException; - if (configurationException != null) + try { - windowManager.ShowMessageBox(String.Format("Configuration Error: {0}", configurationException.Message), "Configuration Error", MessageBoxButton.OK, MessageBoxImage.Error); - e.Handled = true; + var windowManager = this.Container.Get(); + + var configurationException = e.Exception as ConfigurationException; + if (configurationException != null) + { + windowManager.ShowMessageBox(String.Format("Configuration Error: {0}", configurationException.Message), "Configuration Error", MessageBoxButton.OK, MessageBoxImage.Error); + e.Handled = true; + } + else + { + var vm = this.Container.Get(); + vm.Exception = e.Exception; + windowManager.ShowDialog(vm); + } } - else + catch (Exception exception) { - var vm = this.Container.Get(); - vm.Exception = e.Exception; - windowManager.ShowDialog(vm); + // Don't re-throw. Nasty stuff happens if we throw an exception while trying to handle an unhandled exception + // For starters, the event log shows the wrong exception - this one, instead of the root cause + logger.Error("Unhandled exception while trying to display unhandled exception window", exception); + LogManager.Flush(); } } From 59518977d6c266686c4e5aa2764c3311c764bc00 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Tue, 10 Mar 2015 09:25:57 +0000 Subject: [PATCH 14/22] Actually, not convinced LogManager.Flush() does what I want Reading furhter, it looks like setting AutoFlush=True on the NLog target config is the safest thing to do. See #15 --- src/SyncTrayzor/App.config | 1 + src/SyncTrayzor/Bootstrapper.cs | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/SyncTrayzor/App.config b/src/SyncTrayzor/App.config index 2511f9aa..e8a5db77 100644 --- a/src/SyncTrayzor/App.config +++ b/src/SyncTrayzor/App.config @@ -42,6 +42,7 @@ archiveEvery="Day" archiveNumbering="Date" maxArchiveFiles="7" + AutoFlush="True" layout="${longdate} [${level}] ${logger}: ${message} ${exception:format=type,message,method,stacktrace,tostring:maxInnerExceptionLevel=10:innerFormat=shortType,message,method}"/> diff --git a/src/SyncTrayzor/Bootstrapper.cs b/src/SyncTrayzor/Bootstrapper.cs index fe7c6141..90739b2d 100644 --- a/src/SyncTrayzor/Bootstrapper.cs +++ b/src/SyncTrayzor/Bootstrapper.cs @@ -106,7 +106,6 @@ protected override void OnUnhandledException(DispatcherUnhandledExceptionEventAr { var logger = LogManager.GetCurrentClassLogger(); logger.Error("An unhandled exception occurred", e.Exception); - LogManager.Flush(); // If we're shutting down, we're not going to be able to display an error dialog.... // We've logged it. Nothing else we can do. @@ -135,7 +134,6 @@ protected override void OnUnhandledException(DispatcherUnhandledExceptionEventAr // Don't re-throw. Nasty stuff happens if we throw an exception while trying to handle an unhandled exception // For starters, the event log shows the wrong exception - this one, instead of the root cause logger.Error("Unhandled exception while trying to display unhandled exception window", exception); - LogManager.Flush(); } } From 8b3b32a9ee576bc7b77f2a6a29e4f717f580abac Mon Sep 17 00:00:00 2001 From: Antony Male Date: Tue, 10 Mar 2015 09:26:05 +0000 Subject: [PATCH 15/22] Fix typo --- src/SyncTrayzor/Pages/ShellViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SyncTrayzor/Pages/ShellViewModel.cs b/src/SyncTrayzor/Pages/ShellViewModel.cs index 2d8b568e..2601a035 100644 --- a/src/SyncTrayzor/Pages/ShellViewModel.cs +++ b/src/SyncTrayzor/Pages/ShellViewModel.cs @@ -118,7 +118,7 @@ public void ShowExitedWithError() { var msg = "Failed to start Syncthing.\n\n" + "Please read the log to determine the cause.\n\n" + - "If \"FATAL: Cannot open database appears\", please close any other open " + + "If \"FATAL: Cannot open database\" appears, please close any other open " + "instances of Syncthing. If SyncTrayzor crashed previously, there may still be zombine Syncthing " + "processes alive. Please use the menu option \"Syncthing -> Kill all Syncthing processes\" to stop them, then use \"Syncthing -> Start\" to start Syncthing again."; this.windowManager.ShowMessageBox(msg, "Syncthing failed to start", icon: MessageBoxImage.Error); From bbe934bc7f3697e799beae0ad05da1a1aad5628f Mon Sep 17 00:00:00 2001 From: Antony Male Date: Tue, 10 Mar 2015 09:35:41 +0000 Subject: [PATCH 16/22] Add link to wiki Contributing page in README --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5491d952..ad0e16cd 100644 --- a/README.md +++ b/README.md @@ -41,10 +41,18 @@ Grab the latest standalone .zip from the [releases](https://github.com/canton7/S Unzip, and run `SyncTrayzor.exe`. If you're updating, you'll need to copy the `data` folder across from your previous standalone installation. +Contributing +------------ + +Got a bug? [Raise an issue](https://github.com/canton7/SyncTrayzor/issues), providing as much detail as you can. + +Want to make a contribution? Fantastic, and thank you! Please read [Contributing](https://github.com/canton7/SyncTrayzor/wiki/Contributing) first. + + Known Issues ------------ -There are a couple of knows issues with SyncTrayzor. Fixes are on the way, but until then here's a head-up: +There are a couple of knows issues with SyncTrayzor. Fixes are on the way, but until then here's a heads-up: - SyncTrayzor can't currently handle the HTTP Basic Authentication which Syncthing uses to password-protect the GUI. The symptoms are the UI not loading, or showing 'Not Authorized' (if it's not loading, go to Syncthing -> Refresh Browser and 'Not Authorized' should appear). From 5bd33e03f222948d34ddd9f40dfd8a7865dfb8a8 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Tue, 10 Mar 2015 09:37:33 +0000 Subject: [PATCH 17/22] Increase 'squish synced messages' dead time to 10 seconds --- src/SyncTrayzor/NotifyIcon/NotifyIconManager.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/SyncTrayzor/NotifyIcon/NotifyIconManager.cs b/src/SyncTrayzor/NotifyIcon/NotifyIconManager.cs index 027bf304..4be532d1 100644 --- a/src/SyncTrayzor/NotifyIcon/NotifyIconManager.cs +++ b/src/SyncTrayzor/NotifyIcon/NotifyIconManager.cs @@ -25,6 +25,9 @@ public interface INotifyIconManager public class NotifyIconManager : INotifyIconManager { + // Amount of time to squish 'synced' messages for after a connectivity event + private static readonly TimeSpan syncedDeadTime = TimeSpan.FromSeconds(10); + private readonly IViewManager viewManager; private readonly NotifyIconViewModel viewModel; private readonly IApplicationState application; @@ -76,7 +79,7 @@ public NotifyIconManager( this.syncThingManager.FolderSyncStateChanged += (o, e) => { if (this.ShowSynchronizedBalloon && - DateTime.UtcNow - this.syncThingManager.LastConnectivityEventTime > TimeSpan.FromSeconds(5) && + DateTime.UtcNow - this.syncThingManager.LastConnectivityEventTime > syncedDeadTime && e.SyncState == FolderSyncState.Idle && e.PrevSyncState == FolderSyncState.Syncing) { Application.Current.Dispatcher.CheckAccess(); // Double-check From d3b4c692116550083e65957e0024e0b53525ac9e Mon Sep 17 00:00:00 2001 From: Antony Male Date: Tue, 10 Mar 2015 10:19:23 +0000 Subject: [PATCH 18/22] Refactor Rakefile --- Rakefile | 214 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 112 insertions(+), 102 deletions(-) diff --git a/Rakefile b/Rakefile index 57d0c57e..2a3aa1bc 100644 --- a/Rakefile +++ b/Rakefile @@ -6,132 +6,142 @@ rescue LoadError exit 1 end -ISCC = '"C:\Program Files (x86)\Inno Setup 5\ISCC.exe"' +ISCC = 'C:\Program Files (x86)\Inno Setup 5\ISCC.exe' +unless File.exist?(ISCC) + warn "Please install Inno Setup" + exit 1 +end -BIN_DIR_64 = 'bin/x64/Release' -BIN_DIR_86 = 'bin/x86/Release' +CONFIG = ENV['CONFIG'] || 'Release' SRC_DIR = 'src/SyncTrayzor' INSTALLER_DIR = 'installer' -INSTALLER_64 = File.join(INSTALLER_DIR, 'x64') -INSTALLER_86 = File.join(INSTALLER_DIR, 'x86') - -INSTALLER_64_OUTPUT = File.join(INSTALLER_64, 'SyncTrayzorSetup-x64.exe') -INSTALLER_86_OUTPUT = File.join(INSTALLER_86, 'SyncTrayzorSetup-x86.exe') - -PORTABLE_OUTPUT_DIR_64 = File.absolute_path('SyncTrayzorPortable-x64') -PORTABLE_OUTPUT_DIR_86 = File.absolute_path('SyncTrayzorPortable-x86') - -CONFIG = ENV['CONFIG'] || 'Release' - -def cp_to_portable(ouput_dir, src) - dest = File.join(ouput_dir, src) - mkdir_p File.dirname(dest) unless File.exist?(File.dirname(dest)) - cp src, dest +class ArchDirConfig + attr_reader :arch + attr_reader :bin_dir + attr_reader :installer_dir + attr_reader :installer_output + attr_reader :installer_iss + attr_reader :portable_output_dir + + def initialize(arch) + @arch = arch + @bin_dir = "bin/#{@arch}/#{CONFIG}" + @installer_dir = File.join(INSTALLER_DIR, @arch) + @installer_output = File.join(@installer_dir, "SyncTrayzorSetup-#{@arch}.exe") + @installer_iss = File.join(@installer_dir, "installer-#{@arch}.iss") + @portable_output_dir = File.absolute_path("SyncTrayzorPortable-#{@arch}") + end end -desc 'Build the project (64-bit)' -build :buildx64 do |b| - b.sln = 'src/SyncTrayzor.sln' - b.target = [:Clean, :Build] - b.prop 'Configuration', CONFIG - b.prop 'Platform', 'x64' -end +ARCH_CONFIG = [ArchDirConfig.new('x64'), ArchDirConfig.new('x86')] -desc 'Build the project (32-bit)' -build :buildx86 do |b| - b.sln = 'src/SyncTrayzor.sln' - b.target = [:Clean, :Build] - b.prop 'Configuration', CONFIG - b.prop 'Platform', 'x86' +namespace :build do + ARCH_CONFIG.each do |arch_config| + desc "Build the project (#{arch_config.arch})" + build arch_config.arch do |b| + b.sln = 'src/SyncTrayzor.sln' + b.target = [:Clean, :Build] + b.prop 'Configuration', CONFIG + b.prop 'Platform', arch_config.arch + end + end end desc 'Build both 64-bit and 32-bit binaries' -task :build => [:buildx64, :buildx86] - -def create_installer(output_file, installer_dir, iss_name) - rm output_file if File.exist?(output_file) - sh ISCC, File.join(installer_dir, iss_name) +task :build => ARCH_CONFIG.map{ |x| :"build:#{x.arch}" } + +namespace :installer do + ARCH_CONFIG.each do |arch_config| + desc "Create the installer (#{arch_config.arch})" + task arch_config.arch do + rm arch_config.installer_output if File.exist?(arch_config.installer_output) + sh %Q{"#{ISCC}"}, arch_config.installer_iss + end + end end -desc 'Create 64-bit installer' -task :installerx64 do - create_installer(INSTALLER_64_OUTPUT, INSTALLER_64, 'installer-x64.iss') -end +desc 'Build both 64-bit and 32-bit installers' +task :installer => ARCH_CONFIG.map{ |x| :"installer:#{x.arch}" } -desc 'Create 32-bit installer' -task :installerx86 do - create_installer(INSTALLER_86_OUTPUT, INSTALLER_86, 'installer-x86.iss') +def cp_to_portable(ouput_dir, src) + dest = File.join(ouput_dir, src) + mkdir_p File.dirname(dest) unless File.exist?(File.dirname(dest)) + cp src, dest end -desc 'Create 32-bit and 64-bit installers' -task :installer => [:installerx64, :installerx86] - -def create_portable(bin_dir, output_dir, installer_platform_dir) - rm_rf output_dir - mkdir_p output_dir - - Dir.chdir(bin_dir) do - files = FileList[ - '*.exe', - '*.exe.config', - '*.dll', - '*.pdb', - '*.pak', - '*.dat', - File.join('locales', '*'), - ].exclude('*.vshost.*') - - files.each do |file| - cp_to_portable(output_dir, file) +namespace :portable do + ARCH_CONFIG.each do |arch_config| + desc "Create the portable package (#{arch_config.arch})" + task arch_config.arch do + rm_rf arch_config.portable_output_dir + mkdir_p arch_config.portable_output_dir + + Dir.chdir(arch_config.bin_dir) do + files = FileList[ + '*.exe', + '*.exe.config', + '*.dll', + '*.pdb', + '*.pak', + '*.dat', + File.join('locales', '*'), + ].exclude('*.vshost.*') + + files.each do |file| + cp_to_portable(arch_config.portable_output_dir, file) + end + end + + cp File.join(SRC_DIR, 'Icons', 'default.ico'), arch_config.portable_output_dir + + FileList['*.md', '*.txt'].each do |file| + cp_to_portable(arch_config.portable_output_dir, file) + end + + Dir.chdir(arch_config.installer_dir) do + FileList['syncthing.exe', '*.dll'].each do |file| + cp_to_portable(arch_config.portable_output_dir, file) + end + end + + puts 'Rewriting app.config' + config_path = File.join(arch_config.portable_output_dir, 'SyncTrayzor.exe.config') + doc = File.open(config_path, 'r') do |f| + doc = REXML::Document.new(f) + REXML::XPath.first(doc, '/configuration/applicationSettings//setting[@name="PortableMode"]/value').text = 'True' + doc + end + File.open(config_path, 'w') do |f| + doc.write(f) + end end end +end - cp File.join(SRC_DIR, 'Icons', 'default.ico'), output_dir +desc 'Create both 64-bit and 32-bit portable packages' +task :portable => ARCH_CONFIG.map{ |x| :"portable:#{x.arch}" } - FileList['*.md', '*.txt'].each do |file| - cp_to_portable(output_dir, file) - end - - Dir.chdir(installer_platform_dir) do - FileList['syncthing.exe', '*.dll'].each do |file| - cp_to_portable(output_dir, file) +namespace :clean do + ARCH_CONFIG.each do |arch_config| + desc "Clean everything (#{arch_config.arch})" + task arch_config.arch do + rm_rf arch_config.portable_output_dir if File.exist?(arch_config.portable_output_dir) + rm arch_config.installer_output if File.exist?(arch_config.installer_output) end end - - puts 'Rewriting app.config' - config_path = File.join(output_dir, 'SyncTrayzor.exe.config') - doc = File.open(config_path, 'r') do |f| - doc = REXML::Document.new(f) - REXML::XPath.first(doc, '/configuration/applicationSettings//setting[@name="PortableMode"]/value').text = 'True' - doc - end - File.open(config_path, 'w') do |f| - doc.write(f) - end end -desc 'Create the portable (x64) release directory' -task :portablex64 do - create_portable(BIN_DIR_64, PORTABLE_OUTPUT_DIR_64, INSTALLER_64) -end +desc 'Clean portable and installer, all architectures' +task :clean => ARCH_CONFIG.map{ |x| :"clean:#{x.arch}" } -desc 'Create the portable (x86) release directory' -task :portablex86 do - create_portable(BIN_DIR_86, PORTABLE_OUTPUT_DIR_86, INSTALLER_86) +namespace :package do + ARCH_CONFIG.each do |arch_config| + desc "Build installer and portable (#{arch_config.arch})" + task arch_config.arch => [:"clean:#{arch_config.arch}", :"build:#{arch_config.arch}", :"installer:#{arch_config.arch}", :"portable:#{arch_config.arch}"] + end end -desc 'Create portable release directories for x64 and x86' -task :portable => [:portablex64, :portablex86] - -desc 'Build and package everything' -task :package => [:build, :installer, :portable] - -desc 'Remove portable and installer' -task :clean do - rm_rf PORTABLE_OUTPUT_DIR_64 if File.exist?(PORTABLE_OUTPUT_DIR_64) - rm_rf PORTABLE_OUTPUT_DIR_86 if File.exist?(PORTABLE_OUTPUT_DIR_86) - rm INSTALLER_64 if File.exist?(INSTALLER_64) - rm INSTALLER_86 if File.exist?(INSTALLER_86) -end \ No newline at end of file +desc 'Build installer and portable for all architectures' +task :package => ARCH_CONFIG.map{ |x| :"package:#{x.arch}" } From 85653151349aa21ada8745a6ddccb2d4403cb746 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Tue, 10 Mar 2015 12:25:20 +0000 Subject: [PATCH 19/22] Update README --- README.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/README.md b/README.md index ad0e16cd..9ccd36ac 100644 --- a/README.md +++ b/README.md @@ -49,22 +49,6 @@ Got a bug? [Raise an issue](https://github.com/canton7/SyncTrayzor/issues), prov Want to make a contribution? Fantastic, and thank you! Please read [Contributing](https://github.com/canton7/SyncTrayzor/wiki/Contributing) first. -Known Issues ------------- - -There are a couple of knows issues with SyncTrayzor. Fixes are on the way, but until then here's a heads-up: - - - SyncTrayzor can't currently handle the HTTP Basic Authentication which Syncthing uses to password-protect the GUI. - The symptoms are the UI not loading, or showing 'Not Authorized' (if it's not loading, go to Syncthing -> Refresh Browser and 'Not Authorized' should appear). - The short-term fix is to either remove the password-protection on the GUI, or use an external browser (go to Syncthing -> Open External Browser). - See [Issue #13](https://github.com/canton7/SyncTrayzor/issues/13). - - - SyncTrayzor executes Syncthing from AppData, which some anti-malware tools won't allow. The symtom is an exception with the message - "System.ComponentModel.Win32Exception (0x80004005): This program is blocked by group policy. For more information, contact your system administrator" when trying to - start Syncthing. Better error reporting is on its way, but please whitelist `C:\Users\\AppData\Roaming\SyncTrayzor\syncthing.exe` (if you used the installer, - otherwise whereever your portable installation is) in your anti-malware program's settings. See [Issue #12](https://github.com/canton7/SyncTrayzor/issues/12). - - What will SyncTrayzor do to Syncthing? -------------------------------------- From 823ee55adfcbf45dac2665e1e1ffe145f3a49e1e Mon Sep 17 00:00:00 2001 From: Antony Male Date: Tue, 10 Mar 2015 12:50:03 +0000 Subject: [PATCH 20/22] Fix crash in release builds run outside debugger --- src/SyncTrayzor/Bootstrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SyncTrayzor/Bootstrapper.cs b/src/SyncTrayzor/Bootstrapper.cs index 90739b2d..ba2cddc3 100644 --- a/src/SyncTrayzor/Bootstrapper.cs +++ b/src/SyncTrayzor/Bootstrapper.cs @@ -43,7 +43,7 @@ protected override void ConfigureIoC(IStyletIoCBuilder builder) builder.Bind().To().InSingletonScope(); builder.Bind(typeof(IModelValidator<>)).To(typeof(FluentModelValidator<>)); - builder.Bind(typeof(IValidator<>)).ToAllImplementations(); + builder.Bind(typeof(IValidator<>)).ToAllImplementations(this.Assemblies); } protected override void Configure() From b24a3c78368dc51b8def684b9ee8f9a06ffe4c50 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Tue, 10 Mar 2015 12:25:38 +0000 Subject: [PATCH 21/22] Update changelog --- CHANGELOG.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 9143df97..426bccb2 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,17 @@ Changelog ========= +v1.0.7 +------ + + - Support GUI Authentication + - Ignore 'synced' events after device connection/disconnection, reducing noise + - Add option to obfuscate device IDs (thanks Adrian Rudnik) + - Allow Syncthing to localize by sending correct language headers (thanks Adrian Rudnik) + - Add validation to the Settings page + - Better handle exceptions encountered during shutdown + - Catch case where syncthing.exe can't be started because of group policy settings + v1.0.6 ------ From 27b18166e3b8503a711ba6afd984d188456dcdb9 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Tue, 10 Mar 2015 12:26:10 +0000 Subject: [PATCH 22/22] Bump version --- src/SyncTrayzor/Properties/AssemblyInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SyncTrayzor/Properties/AssemblyInfo.cs b/src/SyncTrayzor/Properties/AssemblyInfo.cs index 4b34cf9c..cf712621 100644 --- a/src/SyncTrayzor/Properties/AssemblyInfo.cs +++ b/src/SyncTrayzor/Properties/AssemblyInfo.cs @@ -51,5 +51,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.6.0")] -[assembly: AssemblyFileVersion("1.0.6.0")] +[assembly: AssemblyVersion("1.0.7.0")] +[assembly: AssemblyFileVersion("1.0.7.0")]