diff --git a/DesktopEdge/Assets/Images/processing.png b/DesktopEdge/Assets/Images/processing.png new file mode 100644 index 00000000..e7fcfe4f Binary files /dev/null and b/DesktopEdge/Assets/Images/processing.png differ diff --git a/DesktopEdge/MainWindow.xaml.cs b/DesktopEdge/MainWindow.xaml.cs index a95372fb..8e04e49d 100644 --- a/DesktopEdge/MainWindow.xaml.cs +++ b/DesktopEdge/MainWindow.xaml.cs @@ -648,7 +648,9 @@ namespace ZitiDesktopEdge { } private void MonitorClient_OnCommunicationError(object sender, Exception e) { - ShowError("Communication Error", e.Message); + string msg = "Communication Error with monitor?"; + logger.Warn(msg); + ShowError(msg, e.Message); } private void MainMenu_OnShowBlurb(string message) { @@ -755,7 +757,8 @@ namespace ZitiDesktopEdge { string nextVersionStr = null; private void MonitorClient_OnReconnectFailure(object sender, object e) { - if (nextVersionStr == null) { + logger.Debug("OnReconnectFailure triggered"); + if (nextVersionStr == null) { // check for the current version nextVersionStr = "checking for update"; Version nextVersion = VersionUtil.NormalizeVersion(GithubAPI.GetVersion(GithubAPI.GetJson(GithubAPI.ProdUrl))); @@ -779,6 +782,7 @@ namespace ZitiDesktopEdge { private void MonitorClient_OnShutdownEvent(object sender, StatusEvent e) { + logger.Info("The monitor has indicated the application should shut down."); this.Dispatcher.Invoke(() => { Application.Current.Shutdown(); }); @@ -795,6 +799,7 @@ namespace ZitiDesktopEdge { } logger.Debug("MonitorClient_OnServiceStatusEvent: {0}", evt.Status); Application.Current.Properties["ReleaseStream"] = evt.ReleaseStream; + Application.Current.Properties["AutomaticUpgradeDisabled"] = evt.AutomaticUpgradeDisabled; ServiceControllerStatus status = (ServiceControllerStatus)Enum.Parse(typeof(ServiceControllerStatus), evt.Status); switch (status) { diff --git a/DesktopEdge/Views/Screens/MainMenu.xaml b/DesktopEdge/Views/Screens/MainMenu.xaml index 8f424d0a..bc90f7b6 100644 --- a/DesktopEdge/Views/Screens/MainMenu.xaml +++ b/DesktopEdge/Views/Screens/MainMenu.xaml @@ -64,6 +64,7 @@ + @@ -96,6 +97,10 @@ + + + + diff --git a/DesktopEdge/Views/Screens/MainMenu.xaml.cs b/DesktopEdge/Views/Screens/MainMenu.xaml.cs index df6383be..aaaef3a9 100644 --- a/DesktopEdge/Views/Screens/MainMenu.xaml.cs +++ b/DesktopEdge/Views/Screens/MainMenu.xaml.cs @@ -35,12 +35,18 @@ namespace ZitiDesktopEdge { private string appVersion = null; private bool allowReleaseSelect = false; public double MainHeight = 500; - + private bool isBeta { get { return Application.Current.Properties["ReleaseStream"]?.ToString() == "beta"; } } + private bool automaticUpgradeDisabled { + get + { + return bool.TrueString.ToLower() == ("" + Application.Current.Properties["AutomaticUpgradeDisabled"]?.ToString()).ToLower().Trim(); + } + } public void ShowUpdateAvailable(double timeLeft, DateTime updateTime) { ForceUpdate.Visibility = Visibility.Visible; @@ -124,6 +130,12 @@ namespace ZitiDesktopEdge { UpdateState(); } + private void ShowAutomaticUpgradesMenuAction(object sender, MouseButtonEventArgs e) + { + menuState = "ConfigureAutomaticUpgrades"; + UpdateState(); + } + async private void SetReleaseStreamMenuAction(object sender, MouseButtonEventArgs e) { CheckForUpdateStatus.Visibility = Visibility.Collapsed; TriggerUpdateButton.Visibility = Visibility.Collapsed; @@ -154,6 +166,22 @@ namespace ZitiDesktopEdge { UpdateState(); } + async private void SetAutomaticUpgradesMenuAction(object sender, MouseButtonEventArgs e) { + bool disableAutomaticUpgrades = false; + if (sender == AutomaticUpgradesItemOff) { + disableAutomaticUpgrades = true; + } + var monitorClient = (MonitorClient)Application.Current.Properties["MonitorClient"]; + + SvcResponse r = await monitorClient.SetAutomaticUpgradeDisabledAsync(disableAutomaticUpgrades); + if(r.Code != 0) { + logger.Error(r?.Error); + } else { + Application.Current.Properties["AutomaticUpgradeDisabled"] = disableAutomaticUpgrades; + } + SetAutomaticUpgradesState(disableAutomaticUpgrades); + } + private void checkResponse(SvcResponse r, string titleOnErr, string msgOnErr) { if (r == null) { MainWindow.ShowError(titleOnErr, msgOnErr); @@ -168,7 +196,10 @@ namespace ZitiDesktopEdge { } private void UpdateState() { - IdListScrollView.Height = this.ActualHeight-100.00; + double h = this.ActualHeight - 100.00; + if (h > 0) { + IdListScrollView.Height = h; + } IdListScrollView.Visibility = Visibility.Collapsed; MainItems.Visibility = Visibility.Collapsed; AboutItems.Visibility = Visibility.Collapsed; @@ -181,7 +212,8 @@ namespace ZitiDesktopEdge { ConfigItems.Visibility = Visibility.Collapsed; LogLevelItems.Visibility = Visibility.Collapsed; ReleaseStreamItems.Visibility = Visibility.Collapsed; - + AutomaticUpgradesItems.Visibility = Visibility.Collapsed; + if (menuState == "About") { MenuTitle.Content = "About"; AboutItemsArea.Visibility = Visibility.Visible; @@ -233,12 +265,18 @@ namespace ZitiDesktopEdge { MenuTitle.Content = "Set Release Stream"; ReleaseStreamItems.Visibility = allowReleaseSelect ? Visibility.Visible : Visibility.Collapsed; BackArrow.Visibility = Visibility.Visible; + } else if (menuState == "ConfigureAutomaticUpgrades") { + SetAutomaticUpgradesState(automaticUpgradeDisabled); + + MenuTitle.Content = "Automatic Upgrades"; + AutomaticUpgradesItems.Visibility = Visibility.Visible; + BackArrow.Visibility = Visibility.Visible; } else if (menuState == "Config") { MenuTitle.Content = "Tunnel Configuration"; ConfigItems.Visibility = Visibility.Visible; BackArrow.Visibility = Visibility.Visible; - ConfigPageSize.Value = ((Application.Current.Properties.Contains("ApiPageSize"))?Application.Current.Properties["ApiPageSize"].ToString(): "250"); + ConfigPageSize.Value = ((Application.Current.Properties.Contains("ApiPageSize"))?Application.Current.Properties["ApiPageSize"].ToString(): "25"); ConfigIp.Value = Application.Current.Properties["ip"]?.ToString(); ConfigSubnet.Value = Application.Current.Properties["subnet"]?.ToString(); ConfigMtu.Value = Application.Current.Properties["mtu"]?.ToString(); @@ -278,7 +316,7 @@ namespace ZitiDesktopEdge { } private void GoBack(object sender, MouseButtonEventArgs e) { - if (menuState == "Config" || menuState == "LogLevel" || menuState == "UILogs" || menuState == "SetReleaseStream") { + if (menuState == "Config" || menuState == "LogLevel" || menuState == "UILogs" || menuState == "SetReleaseStream" || menuState == "ConfigureAutomaticUpgrades") { menuState = "Advanced"; } else if (menuState == "Licenses") { menuState = "About"; @@ -391,6 +429,10 @@ namespace ZitiDesktopEdge { this.ReleaseStreamItemBeta.IsSelected = isBeta; this.ReleaseStreamItemStable.IsSelected = !isBeta; } + private void SetAutomaticUpgradesState(bool yesNo) { + this.AutomaticUpgradesItemOn.IsSelected = !yesNo; + this.AutomaticUpgradesItemOff.IsSelected = yesNo; + } async private void CheckForUpdate_Click(object sender, RoutedEventArgs e) { logger.Info("checking for update..."); diff --git a/DesktopEdge/ZitiDesktopEdge.csproj b/DesktopEdge/ZitiDesktopEdge.csproj index e7116423..abdb6a8c 100644 --- a/DesktopEdge/ZitiDesktopEdge.csproj +++ b/DesktopEdge/ZitiDesktopEdge.csproj @@ -473,5 +473,8 @@ + + + \ No newline at end of file diff --git a/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs b/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs index f12a61f6..bc9b44b9 100644 --- a/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs +++ b/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs @@ -478,9 +478,16 @@ namespace ZitiDesktopEdge.DataStructures { } + public class MonitorConfigEvent : ActionEvent + { + public bool AutomaticUpdatesDisabled { get; set; } + } + + public class MonitorServiceStatusEvent : SvcResponse { public string Status { get; set; } public string ReleaseStream { get; set; } + public string AutomaticUpgradeDisabled { get; set; } public bool IsStopped() { return "Stopped" == this.Status; diff --git a/ZitiDesktopEdge.Client/Server/IPCServer.cs b/ZitiDesktopEdge.Client/Server/IPCServer.cs index b3b39133..3c6442b4 100644 --- a/ZitiDesktopEdge.Client/Server/IPCServer.cs +++ b/ZitiDesktopEdge.Client/Server/IPCServer.cs @@ -39,6 +39,9 @@ namespace ZitiDesktopEdge.Server { public delegate SvcResponse TriggerUpdateDelegate(); public TriggerUpdateDelegate TriggerUpdate { get; set; } + public delegate SvcResponse SetAutomaticUpdateDisabledDelegate(bool disabled); + public SetAutomaticUpdateDisabledDelegate SetAutomaticUpdateDisabled { get; set; } + public IPCServer() { ipcPipeName = PipeName; eventPipeName = EventPipeName; @@ -234,6 +237,9 @@ namespace ZitiDesktopEdge.Server { case "triggerupdate": r = TriggerUpdate(); break; + case "setautomaticupgradedisabled": + r = SetAutomaticUpdateDisabled(bool.TrueString.ToLower() == ("" + ae.Action).ToLower().Trim()); + break; default: r.Message = "FAILURE"; r.Code = -3; diff --git a/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs b/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs index 79f58ba8..b8b76a4a 100644 --- a/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs +++ b/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs @@ -4,14 +4,12 @@ using System.IO; using System.IO.Pipes; using System.Security.Principal; using System.Security.AccessControl; -using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using NLog; using ZitiDesktopEdge.DataStructures; -using ZitiDesktopEdge.Server; /// /// The implementation will abstract away the setup of the communication to @@ -413,7 +411,7 @@ namespace ZitiDesktopEdge.ServiceClient { //almost certainly a problem with the pipe - recreate the pipe... //setupPipe(); //throw ioe; - Logger.Error(ioe, "Unexpected error"); + Logger.Error(ioe, "Could not perform ziti dump. Unexpected error. Is ziti running?"); } return; } diff --git a/ZitiDesktopEdge.Client/ServiceClient/MonitorClient.cs b/ZitiDesktopEdge.Client/ServiceClient/MonitorClient.cs index 51a650ca..7e5ff171 100644 --- a/ZitiDesktopEdge.Client/ServiceClient/MonitorClient.cs +++ b/ZitiDesktopEdge.Client/ServiceClient/MonitorClient.cs @@ -67,6 +67,10 @@ namespace ZitiDesktopEdge.ServiceClient { var instEvt = serializer.Deserialize(new JsonTextReader(new StringReader(line))); InstallationNotificationEvent(instEvt); break; + case "Config": + var cfg = serializer.Deserialize(new JsonTextReader(new StringReader(line))); + Logger.Debug("CONFIG EVENT: " + cfg.AutomaticUpdatesDisabled); + break; default: ServiceStatusEvent(evt); break; @@ -135,6 +139,7 @@ namespace ZitiDesktopEdge.ServiceClient { } return null; } + async public Task SetReleaseStreamAsync(string stream) { ActionEvent action = new ActionEvent() { Op = "SetReleaseStream", Action = stream }; try { @@ -179,5 +184,15 @@ namespace ZitiDesktopEdge.ServiceClient { } return null; } + async public Task SetAutomaticUpgradeDisabledAsync(bool disabled) { + ActionEvent action = new ActionEvent() { Op = "SetAutomaticUpgradeDisabled", Action = (disabled ? "true" : "false")}; + try { + await sendAsync(action); + return await readAsync(ipcReader); + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error"); + } + return null; + } } } \ No newline at end of file diff --git a/ZitiUpdateService/Properties/app.manifest b/ZitiUpdateService/Properties/app.manifest index 3b5ed3da..60eec770 100644 --- a/ZitiUpdateService/Properties/app.manifest +++ b/ZitiUpdateService/Properties/app.manifest @@ -19,8 +19,8 @@ - + diff --git a/ZitiUpdateService/UpdateService.cs b/ZitiUpdateService/UpdateService.cs index 6c6cab57..9cf00a84 100644 --- a/ZitiUpdateService/UpdateService.cs +++ b/ZitiUpdateService/UpdateService.cs @@ -21,12 +21,13 @@ using Newtonsoft.Json; using System.Net; using DnsClient; using DnsClient.Protocol; -using ZitiUpdateService.Checkers.PeFile; +using ZitiUpdateService.Utils; using ZitiUpdateService.Checkers; namespace ZitiUpdateService { public partial class UpdateService : ServiceBase { private string betaStreamMarkerFile = "use-beta-stream.txt"; + private static Settings CurrentSettings = new Settings(true); public bool IsBeta { get { @@ -34,6 +35,12 @@ namespace ZitiUpdateService { } private set { } } + public bool AutomaticUpgradeDisabled { + get { + return CurrentSettings.AutomaticUpdatesDisabled; + } + private set { } + } private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); @@ -62,6 +69,10 @@ namespace ZitiUpdateService { public UpdateService() { InitializeComponent(); + + CurrentSettings.Load(); + CurrentSettings.OnConfigurationChange += CurrentSettings_OnConfigurationChange; + base.CanHandlePowerEvent = true; exeLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); @@ -79,6 +90,7 @@ namespace ZitiUpdateService { svr.SetReleaseStream = SetReleaseStream; svr.DoUpdateCheck = DoUpdateCheck; svr.TriggerUpdate = TriggerUpdate; + svr.SetAutomaticUpdateDisabled = SetAutomaticUpdateDisabled; string assemblyVersionStr = Assembly.GetExecutingAssembly().GetName().Version.ToString(); //fetch from ziti? assemblyVersion = new Version(assemblyVersionStr); @@ -90,7 +102,28 @@ namespace ZitiUpdateService { } } - private SvcResponse TriggerUpdate() { + private void CurrentSettings_OnConfigurationChange(object sender, ControllerEvent e) + { + MonitorServiceStatusEvent status = new MonitorServiceStatusEvent() { + Code = 0, + Error = "", + Message = "Configuration Changed", + Status = ServiceActions.ServiceStatus(), + ReleaseStream = IsBeta ? "beta" : "stable", + AutomaticUpgradeDisabled = AutomaticUpgradeDisabled.ToString() + }; + EventRegistry.SendEventToConsumers(status); + } + + private SvcResponse SetAutomaticUpdateDisabled(bool disabled) { + CurrentSettings.AutomaticUpdatesDisabled = disabled; + CurrentSettings.Write(); + SvcResponse r = new SvcResponse(); + r.Message = "Success"; + return r; + } + + private SvcResponse TriggerUpdate() { SvcResponse r = new SvcResponse(); r.Message = "Initiating Update"; @@ -136,7 +169,7 @@ namespace ZitiUpdateService { private void SetLogLevel(string level) { try { Logger.Info("request to change log level received: {0}", level); - if (("" + level).ToLower() == "verbose") + if (("" + level).ToLower().Trim() == "verbose") { level = "trace"; Logger.Info("request to change log level to verbose - but using trace instead"); @@ -445,7 +478,7 @@ namespace ZitiUpdateService { int dnsProbeIntervalInSeconds = 60; bool dnsProbeStarted = false; - private void DnsProbe_Elapsed(object sender, ElapsedEventArgs e) { + private void DnsProbe_Elapsed(object sender, ElapsedEventArgs e) { if (dnsProbeStarted) return; //skip out if it's already going... dnsProbeStarted = true; Logger.Trace("dns probe started"); @@ -501,7 +534,7 @@ namespace ZitiUpdateService { } } - async private Task onEventsClientAsync(StreamWriter writer) { + async private Task onEventsClientAsync(StreamWriter writer) { Logger.Info("a new events client was connected"); //reset to release stream //initial status when connecting the event stream @@ -511,7 +544,8 @@ namespace ZitiUpdateService { Message = "Success", Type = "Status", Status = ServiceActions.ServiceStatus(), - ReleaseStream = IsBeta ? "beta" : "stable" + ReleaseStream = IsBeta ? "beta" : "stable", + AutomaticUpgradeDisabled = AutomaticUpgradeDisabled.ToString() }; await writer.WriteLineAsync(JsonConvert.SerializeObject(status)); await writer.FlushAsync(); @@ -584,8 +618,8 @@ namespace ZitiUpdateService { upInt = new TimeSpan(0, 10, 0); } - if (upInt.TotalMilliseconds < 10 * 60 * 60 * 1000) - { + if (upInt.TotalMilliseconds < 10 * 60 * 1000) + { Logger.Warn("provided time [{0}] is too small. Using 10 minutes.", updateTimerInterval); #if MOCKUPDATE || ALLOWFASTINTERVAL Logger.Info("MOCKUPDATE detected. Not limiting check to 10 minutes"); @@ -636,7 +670,7 @@ namespace ZitiUpdateService { static DateTime mockDate = DateTime.Now; #endif private UpdateCheck getCheck(Version v) - { + { #if MOCKUPDATE //run with MOCKUPDATE to enable debugging/mocking the update check var check = new FilesystemCheck(v, -1, mockDate, "FilesysteCheck.download.mock.txt", new Version("2.1.4")); @@ -724,15 +758,15 @@ namespace ZitiUpdateService { #if !SKIPUPDATE new SignedFileValidator(fileDestination).Verify(); try { - StopZiti(); - StopUI().Wait(); + StopZiti(); + StopUI().Wait(); - Logger.Info("Running update package: " + fileDestination); - // shell out to a new process and run the uninstall, reinstall steps which SHOULD stop this current process as well - Process.Start(fileDestination, "/passive"); - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error during installation"); - } + Logger.Info("Running update package: " + fileDestination); + // shell out to a new process and run the uninstall, reinstall steps which SHOULD stop this current process as well + Process.Start(fileDestination, "/passive"); + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error during installation"); + } #else Logger.Warn("SKIPUPDATE IS SET - NOT PERFORMING UPDATE"); Logger.Warn("SKIPUPDATE IS SET - NOT PERFORMING UPDATE"); @@ -910,7 +944,8 @@ namespace ZitiUpdateService { Code = 0, Error = "SERVICE DOWN", Message = "SERVICE DOWN", - Status = ServiceActions.ServiceStatus() + Status = ServiceActions.ServiceStatus(), + AutomaticUpgradeDisabled = AutomaticUpgradeDisabled.ToString() }; EventRegistry.SendEventToConsumers(status); } else { @@ -921,7 +956,8 @@ namespace ZitiUpdateService { Error = "SERVICE DOWN", Message = "SERVICE DOWN", Status = ServiceActions.ServiceStatus(), - ReleaseStream = IsBeta ? "beta" : "stable" + ReleaseStream = IsBeta ? "beta" : "stable", + AutomaticUpgradeDisabled = AutomaticUpgradeDisabled.ToString() }; EventRegistry.SendEventToConsumers(status); } @@ -966,7 +1002,7 @@ namespace ZitiUpdateService { } private bool InstallationIsCritical(DateTime publishDate) - { + { var installationReminderIntervalStr = ConfigurationManager.AppSettings.Get("InstallationCritical"); var instCritTimespan = TimeSpan.Zero; if (!TimeSpan.TryParse(installationReminderIntervalStr, out instCritTimespan)) diff --git a/ZitiUpdateService/ZitiUpdateService.csproj b/ZitiUpdateService/ZitiUpdateService.csproj index e8828d5b..5af66053 100644 --- a/ZitiUpdateService/ZitiUpdateService.csproj +++ b/ZitiUpdateService/ZitiUpdateService.csproj @@ -61,7 +61,7 @@ - true + false LocalIntranet @@ -160,6 +160,7 @@ ProjectInstaller.cs + Component diff --git a/ZitiUpdateService/checkers/GithubCheck.cs b/ZitiUpdateService/checkers/GithubCheck.cs index 7e213c46..fa456f44 100644 --- a/ZitiUpdateService/checkers/GithubCheck.cs +++ b/ZitiUpdateService/checkers/GithubCheck.cs @@ -97,6 +97,7 @@ namespace ZitiUpdateService.Checkers { string computed = BitConverter.ToString(sha256bytes).Replace("-", ""); File.Delete(sha256dest); + Logger.Info("comparing computed hash: {0} to downloaded hash: {1}", computed, hash); return computed.ToLower().Trim() == hash.ToLower().Trim(); } } diff --git a/ZitiUpdateService/utils/Settings.cs b/ZitiUpdateService/utils/Settings.cs new file mode 100644 index 00000000..1d95f209 --- /dev/null +++ b/ZitiUpdateService/utils/Settings.cs @@ -0,0 +1,119 @@ +using System.IO; +using Newtonsoft.Json; +using NLog; +using ZitiDesktopEdge.DataStructures; + +namespace ZitiUpdateService.Utils +{ + internal class Settings + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private FileSystemWatcher watcher; + + [JsonIgnore] + private string Location { get; set; } + + public bool AutomaticUpdatesDisabled { get; set; } + + public event System.EventHandler OnConfigurationChange; + + internal Settings(bool doInit) + { + if (doInit) + { + init(); + } + } + + public Settings() + { + } + + private void init() + { + string folder = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), "NetFoundry", "ZitiUpdateService"); + string file = "settings.json"; + Location = Path.Combine(folder, file); + Directory.CreateDirectory(folder); + watcher = new FileSystemWatcher(folder); + watcher.Filter = file; + + watcher.NotifyFilter = NotifyFilters.Attributes + | NotifyFilters.CreationTime + | NotifyFilters.DirectoryName + | NotifyFilters.FileName + | NotifyFilters.LastAccess + | NotifyFilters.LastWrite + | NotifyFilters.Security + | NotifyFilters.Size; + watcher.Changed += OnChanged; + watcher.Deleted += OnDeleted; + watcher.Renamed += OnRenamed; + watcher.Error += OnError; + watcher.EnableRaisingEvents = true; + } + + private static JsonSerializer serializer = new JsonSerializer() { Formatting = Formatting.Indented }; + internal void Load() + { + try + { + string json = File.ReadAllText(Location); + var jsonReaderEvt = new JsonTextReader(new StringReader(json)); + Settings s = serializer.Deserialize(jsonReaderEvt); + Update(s); + } + catch + { + // do nothing + } + } + internal void Write() + { + try + { + using (StreamWriter file = File.CreateText(Location)) + { + serializer.Serialize(file, this); + } + this.OnConfigurationChange?.Invoke(null, null); + } + catch + { + // do nothing + } + } + + + private static void OnError(object sender, ErrorEventArgs e) + { + } + + private void OnRenamed(object sender, RenamedEventArgs e) + { + Logger.Info("Settings file renamed. Resetting to defaults..."); + this.Update(new Settings()); + } + + private void OnDeleted(object sender, FileSystemEventArgs e) + { + Logger.Info("Settings file deleted. Resetting to defaults..."); + this.Update(new Settings()); + } + + private void OnCreated(object sender, FileSystemEventArgs e) + { + } + + private void OnChanged(object sender, FileSystemEventArgs e) + { + Logger.Info("Settings file changed. Reloading..."); + this.Load(); + } + + private void Update(Settings source) + { + this.AutomaticUpdatesDisabled = source.AutomaticUpdatesDisabled; + } + } +}