Horizon – Diff between revs 21 and 23

Subversion Repositories:
Rev:
Only display areas with differencesIgnore whitespace
Rev 21 Rev 23
1 using System; 1 using System;
2 using System.Collections.Generic; 2 using System.Collections.Generic;
3 using System.Collections.Specialized; 3 using System.Collections.Specialized;
4 using System.ComponentModel; 4 using System.ComponentModel;
5 using System.Data.SQLite; 5 using System.Data.SQLite;
6 using System.Diagnostics; 6 using System.Diagnostics;
7 using System.Drawing; 7 using System.Drawing;
8 using System.IO; 8 using System.IO;
9 using System.Linq; 9 using System.Linq;
10 using System.Net; 10 using System.Net;
11 using System.Reflection; 11 using System.Reflection;
12 using System.Text; 12 using System.Text;
13 using System.Threading; 13 using System.Threading;
14 using System.Threading.Tasks; 14 using System.Threading.Tasks;
15 using System.Threading.Tasks.Dataflow; 15 using System.Threading.Tasks.Dataflow;
16 using System.Windows.Forms; 16 using System.Windows.Forms;
17 using Horizon.Database; 17 using Horizon.Database;
18 using Horizon.Snapshots; 18 using Horizon.Snapshots;
19 using Horizon.Utilities; 19 using Horizon.Utilities;
20 using Horizon.Utilities.Serialization; 20 using Horizon.Utilities.Serialization;
21 using Mono.Zeroconf; 21 using Mono.Zeroconf;
22 using NetSparkleUpdater; 22 using NetSparkleUpdater;
23 using NetSparkleUpdater.Enums; 23 using NetSparkleUpdater.Enums;
24 using NetSparkleUpdater.SignatureVerifiers; 24 using NetSparkleUpdater.SignatureVerifiers;
25 using NetSparkleUpdater.UI.WinForms; 25 using NetSparkleUpdater.UI.WinForms;
26 using Newtonsoft.Json; 26 using Newtonsoft.Json;
27 using Serilog; 27 using Serilog;
28 using TrackedFolders; 28 using TrackedFolders;
29 using WatsonTcp; 29 using WatsonTcp;
30 using static Horizon.Utilities.Networking.Miscellaneous; 30 using static Horizon.Utilities.Networking.Miscellaneous;
31 using CaptureMode = Configuration.CaptureMode; 31 using CaptureMode = Configuration.CaptureMode;
32 using Path = System.IO.Path; 32 using Path = System.IO.Path;
33   33  
34 namespace Horizon 34 namespace Horizon
35 { 35 {
36 public partial class MainForm : Form 36 public partial class MainForm : Form
37 { 37 {
38 #region Public Enums, Properties and Fields 38 #region Public Enums, Properties and Fields
39   39  
40 public List<FileSystemWatcher> FileSystemWatchers { get; } 40 public List<FileSystemWatcher> FileSystemWatchers { get; }
41   41  
42 public TrackedFolders.TrackedFolders TrackedFolders { get; set; } 42 public TrackedFolders.TrackedFolders TrackedFolders { get; set; }
43   43  
44 public Configuration.Configuration Configuration { get; set; } 44 public Configuration.Configuration Configuration { get; set; }
45   45  
46 public ScheduledContinuation ChangedConfigurationContinuation { get; set; } 46 public ScheduledContinuation ChangedConfigurationContinuation { get; set; }
47   47  
48 #endregion 48 #endregion
49   49  
50 #region Static Fields and Constants 50 #region Static Fields and Constants
51   51  
52 private static SemaphoreSlim _changedFilesLock; 52 private static SemaphoreSlim _changedFilesLock;
53   53  
54 private static HashSet<string> _changedFiles; 54 private static HashSet<string> _changedFiles;
55   55  
56 private static ScheduledContinuation _changedFilesContinuation; 56 private static ScheduledContinuation _changedFilesContinuation;
57   57  
58 #endregion 58 #endregion
59   59  
60 #region Private Delegates, Events, Enums, Properties, Indexers and Fields 60 #region Private Delegates, Events, Enums, Properties, Indexers and Fields
61   61  
62 private readonly CancellationToken _cancellationToken; 62 private readonly CancellationToken _cancellationToken;
63   63  
64 private readonly CancellationTokenSource _cancellationTokenSource; 64 private readonly CancellationTokenSource _cancellationTokenSource;
65   65  
66 private AboutForm _aboutForm; 66 private AboutForm _aboutForm;
67   67  
68 private ManageFoldersForm _manageFoldersForm; 68 private ManageFoldersForm _manageFoldersForm;
69   69  
70 private readonly SnapshotDatabase _snapshotDatabase; 70 private readonly SnapshotDatabase _snapshotDatabase;
71   71  
72 private SnapshotManagerForm _snapshotManagerForm; 72 private SnapshotManagerForm _snapshotManagerForm;
73   73  
74 private readonly SparkleUpdater _sparkle; 74 private readonly SparkleUpdater _sparkle;
75   75  
76 private LogViewForm _logViewForm; 76 private LogViewForm _logViewForm;
77   77  
78 private readonly LogMemorySink _memorySink; 78 private readonly LogMemorySink _memorySink;
79   79  
80 private RegisterService _horizonDiscoveryService; 80 private RegisterService _horizonDiscoveryService;
81   81  
82 private WatsonTcpServer _horizonNetworkShare; 82 private WatsonTcpServer _horizonNetworkShare;
83   83  
84 public bool MemorySinkEnabled { get; set; } 84 public bool MemorySinkEnabled { get; set; }
85   85  
86 #endregion 86 #endregion
87   87  
88 #region Constructors, Destructors and Finalizers 88 #region Constructors, Destructors and Finalizers
89   89  
90 public MainForm(Mutex mutex) : this() 90 public MainForm(Mutex mutex) : this()
91 { 91 {
92 _memorySink = new LogMemorySink(); 92 _memorySink = new LogMemorySink();
93   93  
94 Log.Logger = new LoggerConfiguration() 94 Log.Logger = new LoggerConfiguration()
95 .MinimumLevel.Debug() 95 .MinimumLevel.Debug()
96 .WriteTo.Conditional(condition => MemorySinkEnabled, configureSink => configureSink.Sink(_memorySink)) 96 .WriteTo.Conditional(condition => MemorySinkEnabled, configureSink => configureSink.Sink(_memorySink))
97 .WriteTo.File(Path.Combine(Constants.UserApplicationDirectory, "Logs", $"{Constants.AssemblyName}.log"), 97 .WriteTo.File(Path.Combine(Constants.UserApplicationDirectory, "Logs", $"{Constants.AssemblyName}.log"),
98 rollingInterval: RollingInterval.Day) 98 rollingInterval: RollingInterval.Day)
99 .CreateLogger(); 99 .CreateLogger();
100   100  
101 _snapshotDatabase = new SnapshotDatabase(_cancellationToken); 101 _snapshotDatabase = new SnapshotDatabase(_cancellationToken);
102 _snapshotDatabase.SnapshotRevert += SnapshotDatabase_SnapshotRevert; 102 _snapshotDatabase.SnapshotRevert += SnapshotDatabase_SnapshotRevert;
103 _snapshotDatabase.SnapshotCreate += SnapshotDatabase_SnapshotCreate; 103 _snapshotDatabase.SnapshotCreate += SnapshotDatabase_SnapshotCreate;
-   104 _snapshotDatabase.SnapshotTransferReceived += SnapshotDatabase_SnapshotTransferReceived;
104   105  
105 TrackedFolders = new TrackedFolders.TrackedFolders(); 106 TrackedFolders = new TrackedFolders.TrackedFolders();
106 TrackedFolders.Folder.CollectionChanged += Folder_CollectionChanged; 107 TrackedFolders.Folder.CollectionChanged += Folder_CollectionChanged;
107   108  
108 // Start application update. 109 // Start application update.
109 var manifestModuleName = Assembly.GetEntryAssembly().ManifestModule.FullyQualifiedName; 110 var manifestModuleName = Assembly.GetEntryAssembly().ManifestModule.FullyQualifiedName;
110 var icon = Icon.ExtractAssociatedIcon(manifestModuleName); 111 var icon = Icon.ExtractAssociatedIcon(manifestModuleName);
111   112  
112 _sparkle = new SparkleUpdater("https://horizon.grimore.org/update/appcast.xml", 113 _sparkle = new SparkleUpdater("https://horizon.grimore.org/update/appcast.xml",
113 new Ed25519Checker(SecurityMode.Strict, "LonrgxVjSF0GnY4hzwlRJnLkaxnDn2ikdmOifILzLJY=")) 114 new Ed25519Checker(SecurityMode.Strict, "LonrgxVjSF0GnY4hzwlRJnLkaxnDn2ikdmOifILzLJY="))
114 { 115 {
115 UIFactory = new UIFactory(icon), 116 UIFactory = new UIFactory(icon),
116 RelaunchAfterUpdate = true, 117 RelaunchAfterUpdate = true,
117 SecurityProtocolType = SecurityProtocolType.Tls12 118 SecurityProtocolType = SecurityProtocolType.Tls12
118 }; 119 };
119 _sparkle.StartLoop(true, true); 120 _sparkle.StartLoop(true, true);
120 } 121 }
121   122  
122 public MainForm() 123 public MainForm()
123 { 124 {
124 InitializeComponent(); 125 InitializeComponent();
125   126  
126 _cancellationTokenSource = new CancellationTokenSource(); 127 _cancellationTokenSource = new CancellationTokenSource();
127 _cancellationToken = _cancellationTokenSource.Token; 128 _cancellationToken = _cancellationTokenSource.Token;
128   129  
129 _changedFilesLock = new SemaphoreSlim(1, 1); 130 _changedFilesLock = new SemaphoreSlim(1, 1);
130 FileSystemWatchers = new List<FileSystemWatcher>(); 131 FileSystemWatchers = new List<FileSystemWatcher>();
131   132  
132 _changedFiles = new HashSet<string>(); 133 _changedFiles = new HashSet<string>();
133 _changedFilesContinuation = new ScheduledContinuation(); 134 _changedFilesContinuation = new ScheduledContinuation();
134   135  
135 ChangedConfigurationContinuation = new ScheduledContinuation(); 136 ChangedConfigurationContinuation = new ScheduledContinuation();
136 } 137 }
137   138  
138 /// <summary> 139 /// <summary>
139 /// Clean up any resources being used. 140 /// Clean up any resources being used.
140 /// </summary> 141 /// </summary>
141 /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> 142 /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
142 protected override void Dispose(bool disposing) 143 protected override void Dispose(bool disposing)
143 { 144 {
144 if (disposing && components != null) 145 if (disposing && components != null)
145 { 146 {
146 components.Dispose(); 147 components.Dispose();
147 } 148 }
148   149  
149 _cancellationTokenSource.Cancel(); 150 _cancellationTokenSource.Cancel();
150   151  
151 _snapshotDatabase.SnapshotRevert -= SnapshotDatabase_SnapshotRevert; 152 _snapshotDatabase.SnapshotRevert -= SnapshotDatabase_SnapshotRevert;
152 _snapshotDatabase.SnapshotCreate -= SnapshotDatabase_SnapshotCreate; 153 _snapshotDatabase.SnapshotCreate -= SnapshotDatabase_SnapshotCreate;
-   154 _snapshotDatabase.SnapshotTransferReceived -= SnapshotDatabase_SnapshotTransferReceived;
153   155  
154 _snapshotDatabase.Dispose(); 156 _snapshotDatabase.Dispose();
155   157  
156 base.Dispose(disposing); 158 base.Dispose(disposing);
157 } 159 }
158   160  
159 #endregion 161 #endregion
160   162  
161 #region Event Handlers 163 #region Event Handlers
162   164  
163 private void networkSharingToolStripMenuItem_CheckStateChanged(object sender, EventArgs e) 165 private void networkSharingToolStripMenuItem_CheckStateChanged(object sender, EventArgs e)
164 { 166 {
165 var toolStripMenuItem = (ToolStripMenuItem)sender; 167 var toolStripMenuItem = (ToolStripMenuItem)sender;
166   168  
167 switch (toolStripMenuItem.CheckState) 169 switch (toolStripMenuItem.CheckState)
168 { 170 {
169 case CheckState.Checked: 171 case CheckState.Checked:
170 var freePort = GetAvailableTcpPort(); 172 var freePort = GetAvailableTcpPort();
171   173  
172 _horizonNetworkShare = new WatsonTcpServer("0.0.0.0", freePort); 174 _horizonNetworkShare = new WatsonTcpServer("0.0.0.0", freePort);
173 _horizonNetworkShare.Events.ClientConnected += Events_ClientConnected; 175 _horizonNetworkShare.Events.ClientConnected += Events_ClientConnected;
174 _horizonNetworkShare.Events.ClientDisconnected += Events_ClientDisconnected; 176 _horizonNetworkShare.Events.ClientDisconnected += Events_ClientDisconnected;
175 _horizonNetworkShare.Events.MessageReceived += Events_MessageReceived; 177 _horizonNetworkShare.Events.MessageReceived += Events_MessageReceived;
176 _horizonNetworkShare.Events.ExceptionEncountered += Events_ExceptionEncountered; 178 _horizonNetworkShare.Events.ExceptionEncountered += Events_ExceptionEncountered;
177 #pragma warning disable CS4014 179 #pragma warning disable CS4014
178 _horizonNetworkShare.Start(); 180 _horizonNetworkShare.Start();
179 #pragma warning restore CS4014 181 #pragma warning restore CS4014
180   182  
181 try 183 try
182 { 184 {
183 _horizonDiscoveryService = new RegisterService(); 185 _horizonDiscoveryService = new RegisterService();
184 _horizonDiscoveryService.Name = $"Horizon ({Environment.MachineName})"; 186 _horizonDiscoveryService.Name = $"Horizon ({Environment.MachineName})";
185 _horizonDiscoveryService.RegType = "_horizon._tcp"; 187 _horizonDiscoveryService.RegType = "_horizon._tcp";
186 _horizonDiscoveryService.ReplyDomain = "local."; 188 _horizonDiscoveryService.ReplyDomain = "local.";
187 _horizonDiscoveryService.UPort = freePort; 189 _horizonDiscoveryService.UPort = freePort;
188 _horizonDiscoveryService.Register(); 190 _horizonDiscoveryService.Register();
189 } 191 }
190 catch (Exception exception) 192 catch (Exception exception)
191 { 193 {
192 Log.Error(exception, "Service discovery protocol could not be stared."); 194 Log.Error(exception, "Service discovery protocol could not be stared.");
193 } 195 }
194   196  
195 Configuration.NetworkSharing = true; 197 Configuration.NetworkSharing = true;
196 break; 198 break;
197 case CheckState.Unchecked: 199 case CheckState.Unchecked:
198 if (_horizonNetworkShare != null) 200 if (_horizonNetworkShare != null)
199 { 201 {
200 _horizonNetworkShare.Events.ClientConnected -= Events_ClientConnected; 202 _horizonNetworkShare.Events.ClientConnected -= Events_ClientConnected;
201 _horizonNetworkShare.Events.ClientDisconnected -= Events_ClientDisconnected; 203 _horizonNetworkShare.Events.ClientDisconnected -= Events_ClientDisconnected;
202 _horizonNetworkShare.Events.MessageReceived -= Events_MessageReceived; 204 _horizonNetworkShare.Events.MessageReceived -= Events_MessageReceived;
203 _horizonNetworkShare.Events.ExceptionEncountered -= Events_ExceptionEncountered; 205 _horizonNetworkShare.Events.ExceptionEncountered -= Events_ExceptionEncountered;
204   206  
205 _horizonNetworkShare.Dispose(); 207 _horizonNetworkShare.Dispose();
206 _horizonNetworkShare = null; 208 _horizonNetworkShare = null;
207 } 209 }
208   210  
209 if (_horizonDiscoveryService != null) 211 if (_horizonDiscoveryService != null)
210 { 212 {
211 _horizonDiscoveryService.Dispose(); 213 _horizonDiscoveryService.Dispose();
212 _horizonDiscoveryService = null; 214 _horizonDiscoveryService = null;
213   215  
214 } 216 }
215   217  
216 Configuration.NetworkSharing = false; 218 Configuration.NetworkSharing = false;
217 break; 219 break;
218 } 220 }
219   221  
220 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1), 222 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
221 async () => { await SaveConfiguration(); }, _cancellationToken); 223 async () => { await SaveConfiguration(); }, _cancellationToken);
222 } 224 }
223   225  
224 private void Events_ExceptionEncountered(object sender, ExceptionEventArgs e) 226 private void Events_ExceptionEncountered(object sender, ExceptionEventArgs e)
225 { 227 {
226 Log.Error(e.Exception,$"Client threw exception."); 228 Log.Error(e.Exception,$"Client threw exception.");
227 } 229 }
228   230  
229 private async void Events_MessageReceived(object sender, MessageReceivedEventArgs e) 231 private async void Events_MessageReceived(object sender, MessageReceivedEventArgs e)
230 { 232 {
231 Log.Information($"Client {e.Client?.IpPort} sent {e.Data?.Length} bytes via network sharing."); 233 Log.Information($"Client {e.Client?.IpPort} sent {e.Data?.Length} bytes via network sharing.");
232   234  
233 if (e.Data?.Length == 0) 235 if (e.Data?.Length == 0)
234 { 236 {
235 return; 237 return;
236 } 238 }
237   239  
238 try 240 try
239 { 241 {
240 var payload = Encoding.UTF8.GetString(e.Data); 242 var payload = Encoding.UTF8.GetString(e.Data);
241   243  
242 var completeSnapshot = JsonConvert.DeserializeObject<TransferSnapshot>(payload); 244 var completeSnapshot = JsonConvert.DeserializeObject<TransferSnapshot>(payload);
243   245  
244 await _snapshotDatabase.ApplyTransferSnapshotAsync(completeSnapshot, _cancellationToken); 246 await _snapshotDatabase.ApplyTransferSnapshotAsync(completeSnapshot, _cancellationToken);
245   247  
246 Log.Information($"Stored {completeSnapshot.Name} from {e.Client?.IpPort}"); 248 Log.Information($"Stored {completeSnapshot.Name} from {e.Client?.IpPort}");
247 } 249 }
248 catch (Exception exception) 250 catch (Exception exception)
249 { 251 {
250 Log.Error(exception, $"Failed to process network share from {e.Client?.IpPort}."); 252 Log.Error(exception, $"Failed to process network share from {e.Client?.IpPort}.");
251 } 253 }
252 } 254 }
253   255  
254 private void Events_ClientDisconnected(object sender, WatsonTcp.DisconnectionEventArgs e) 256 private void Events_ClientDisconnected(object sender, WatsonTcp.DisconnectionEventArgs e)
255 { 257 {
256 Log.Information($"Client {e.Client?.IpPort} disconnected from network sharing."); 258 Log.Information($"Client {e.Client?.IpPort} disconnected from network sharing.");
257 } 259 }
258   260  
259 private void Events_ClientConnected(object sender, WatsonTcp.ConnectionEventArgs e) 261 private void Events_ClientConnected(object sender, WatsonTcp.ConnectionEventArgs e)
260 { 262 {
261 Log.Information($"Client {e.Client?.IpPort} connected to network sharing."); 263 Log.Information($"Client {e.Client?.IpPort} connected to network sharing.");
262 } 264 }
263   265  
264 private void WindowToolStripMenuItem_Click(object sender, EventArgs e) 266 private void WindowToolStripMenuItem_Click(object sender, EventArgs e)
265 { 267 {
266 windowToolStripMenuItem.Checked = true; 268 windowToolStripMenuItem.Checked = true;
267 screenToolStripMenuItem.Checked = false; 269 screenToolStripMenuItem.Checked = false;
268   270  
269 Configuration.CaptureMode = CaptureMode.Window; 271 Configuration.CaptureMode = CaptureMode.Window;
270   272  
271 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1), 273 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
272 async () => { await SaveConfiguration(); }, _cancellationToken); 274 async () => { await SaveConfiguration(); }, _cancellationToken);
273 } 275 }
274   276  
275 private void ScreenToolStripMenuItem_Click(object sender, EventArgs e) 277 private void ScreenToolStripMenuItem_Click(object sender, EventArgs e)
276 { 278 {
277 screenToolStripMenuItem.Checked = true; 279 screenToolStripMenuItem.Checked = true;
278 windowToolStripMenuItem.Checked = false; 280 windowToolStripMenuItem.Checked = false;
279   281  
280 Configuration.CaptureMode = CaptureMode.Screen; 282 Configuration.CaptureMode = CaptureMode.Screen;
281   283  
282 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1), 284 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
283 async () => { await SaveConfiguration(); }, _cancellationToken); 285 async () => { await SaveConfiguration(); }, _cancellationToken);
284 } 286 }
285   287  
286 private void LogViewToolStripMenuItem_Click(object sender, EventArgs e) 288 private void LogViewToolStripMenuItem_Click(object sender, EventArgs e)
287 { 289 {
288 if (_logViewForm != null) 290 if (_logViewForm != null)
289 { 291 {
290 return; 292 return;
291 } 293 }
292   294  
293 _logViewForm = new LogViewForm(this, _memorySink, _cancellationToken); 295 _logViewForm = new LogViewForm(this, _memorySink, _cancellationToken);
294 _logViewForm.Closing += LogViewFormClosing; 296 _logViewForm.Closing += LogViewFormClosing;
295 _logViewForm.Show(); 297 _logViewForm.Show();
296 } 298 }
297   299  
298 private void LogViewFormClosing(object sender, CancelEventArgs e) 300 private void LogViewFormClosing(object sender, CancelEventArgs e)
299 { 301 {
300 if (_logViewForm == null) 302 if (_logViewForm == null)
301 { 303 {
302 return; 304 return;
303 } 305 }
304   306  
305 _logViewForm.Closing -= LogViewFormClosing; 307 _logViewForm.Closing -= LogViewFormClosing;
306 _logViewForm.Close(); 308 _logViewForm.Close();
307 _logViewForm = null; 309 _logViewForm = null;
308 } 310 }
309   311  
310 private void SnapshotDatabase_SnapshotCreate(object sender, SnapshotCreateEventArgs e) 312 private void SnapshotDatabase_SnapshotCreate(object sender, SnapshotCreateEventArgs e)
311 { 313 {
312 switch (e) 314 switch (e)
313 { 315 {
314 case SnapshotCreateSuccessEventArgs snapshotCreateSuccessEventArgs: 316 case SnapshotCreateSuccessEventArgs snapshotCreateSuccessEventArgs:
315 if (Configuration.ShowBalloonTooltips) 317 if (Configuration.ShowBalloonTooltips)
316 { 318 {
317 ShowBalloon("Snapshot Succeeded", $"Took a snapshot of {snapshotCreateSuccessEventArgs.Path}.", 319 ShowBalloon("Snapshot Succeeded", $"Took a snapshot of {snapshotCreateSuccessEventArgs.Path}.",
318 5000); 320 5000);
319 } 321 }
320   322  
321 Log.Information($"Took a snapshot of {snapshotCreateSuccessEventArgs.Path}."); 323 Log.Information($"Took a snapshot of {snapshotCreateSuccessEventArgs.Path}.");
322   324  
323 break; 325 break;
324 case SnapshotCreateFailureEventArgs snapshotCreateFailureEventArgs: 326 case SnapshotCreateFailureEventArgs snapshotCreateFailureEventArgs:
325 if (Configuration.ShowBalloonTooltips) 327 if (Configuration.ShowBalloonTooltips)
326 { 328 {
327 ShowBalloon("Snapshot Failed", 329 ShowBalloon("Snapshot Failed",
328 $"Failed to take a snapshot of {snapshotCreateFailureEventArgs.Path}.", 5000); 330 $"Failed to take a snapshot of {snapshotCreateFailureEventArgs.Path}.", 5000);
329 } 331 }
330   332  
331 Log.Information($"Failed to take a snapshot of {snapshotCreateFailureEventArgs.Path}."); 333 Log.Information($"Failed to take a snapshot of {snapshotCreateFailureEventArgs.Path}.");
-   334  
-   335 break;
-   336 }
-   337 }
-   338  
-   339 private void SnapshotDatabase_SnapshotTransferReceived(object sender, SnapshotCreateEventArgs e)
-   340 {
-   341 switch (e)
-   342 {
-   343 case SnapshotCreateSuccessEventArgs snapshotCreateSuccessEventArgs:
-   344 if (Configuration.ShowBalloonTooltips)
-   345 {
-   346 ShowBalloon("Snapshot Transfer Success", $"A snapshot has been transferred {snapshotCreateSuccessEventArgs.Path}.",
-   347 5000);
-   348 }
-   349  
-   350 Log.Information($"A snapshot transfer succeeded {snapshotCreateSuccessEventArgs.Path}.");
-   351  
-   352 break;
-   353 case SnapshotCreateFailureEventArgs snapshotCreateFailureEventArgs:
-   354 if (Configuration.ShowBalloonTooltips)
-   355 {
-   356 ShowBalloon("Snapshot Transfer Failure",
-   357 $"A snapshot failed to transfer {snapshotCreateFailureEventArgs.Path}.", 5000);
-   358 }
-   359  
-   360 Log.Information($"A snapshot failed to transfer {snapshotCreateFailureEventArgs.Path}.");
332   361  
333 break; 362 break;
334 } 363 }
335 } 364 }
336   365  
337 private void SnapshotDatabase_SnapshotRevert(object sender, SnapshotRevertEventArgs e) 366 private void SnapshotDatabase_SnapshotRevert(object sender, SnapshotRevertEventArgs e)
338 { 367 {
339 switch (e) 368 switch (e)
340 { 369 {
341 case SnapshotRevertSuccessEventArgs snapshotRevertSuccessEventArgs: 370 case SnapshotRevertSuccessEventArgs snapshotRevertSuccessEventArgs:
342 if (Configuration.ShowBalloonTooltips) 371 if (Configuration.ShowBalloonTooltips)
343 { 372 {
344 ShowBalloon("Revert Succeeded", $"File {snapshotRevertSuccessEventArgs.Name} reverted.", 5000); 373 ShowBalloon("Revert Succeeded", $"File {snapshotRevertSuccessEventArgs.Name} reverted.", 5000);
345 } 374 }
346   375  
347 Log.Information($"File {snapshotRevertSuccessEventArgs.Name} reverted."); 376 Log.Information($"File {snapshotRevertSuccessEventArgs.Name} reverted.");
348   377  
349 break; 378 break;
350 case SnapshotRevertFailureEventArgs snapshotRevertFailureEventArgs: 379 case SnapshotRevertFailureEventArgs snapshotRevertFailureEventArgs:
351 if (Configuration.ShowBalloonTooltips) 380 if (Configuration.ShowBalloonTooltips)
352 { 381 {
353 ShowBalloon("Revert Failed", $"Reverting file {snapshotRevertFailureEventArgs.Name} failed.", 382 ShowBalloon("Revert Failed", $"Reverting file {snapshotRevertFailureEventArgs.Name} failed.",
354 5000); 383 5000);
355 } 384 }
356   385  
357 Log.Information($"Reverting file {snapshotRevertFailureEventArgs.Name} failed."); 386 Log.Information($"Reverting file {snapshotRevertFailureEventArgs.Name} failed.");
358   387  
359 break; 388 break;
360 } 389 }
361 } 390 }
362   391  
363 private async void Folder_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 392 private async void Folder_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
364 { 393 {
365 if (e.OldItems != null) 394 if (e.OldItems != null)
366 { 395 {
367 foreach (var item in e.OldItems.OfType<Folder>()) 396 foreach (var item in e.OldItems.OfType<Folder>())
368 { 397 {
369 RemoveWatcher(item.Path); 398 RemoveWatcher(item.Path);
370 } 399 }
371 } 400 }
372   401  
373 if (e.NewItems != null) 402 if (e.NewItems != null)
374 { 403 {
375 foreach (var item in e.NewItems.OfType<Folder>()) 404 foreach (var item in e.NewItems.OfType<Folder>())
376 { 405 {
377 // If the folder is not enabled then do not add watchers for the path. 406 // If the folder is not enabled then do not add watchers for the path.
378 if (!item.Enable) 407 if (!item.Enable)
379 { 408 {
380 continue; 409 continue;
381 } 410 }
382   411  
383 if (Directory.Exists(item.Path)) 412 if (Directory.Exists(item.Path))
384 { 413 {
385 AddWatcher(item.Path, item.Recursive); 414 AddWatcher(item.Path, item.Recursive);
386 } 415 }
387 } 416 }
388 } 417 }
389   418  
390 await SaveFolders(); 419 await SaveFolders();
391 } 420 }
392   421  
393 private async void MainForm_Load(object sender, EventArgs e) 422 private async void MainForm_Load(object sender, EventArgs e)
394 { 423 {
395 // Load configuration. 424 // Load configuration.
396 Configuration = await LoadConfiguration(); 425 Configuration = await LoadConfiguration();
397 launchOnBootToolStripMenuItem.Checked = Configuration.LaunchOnBoot; 426 launchOnBootToolStripMenuItem.Checked = Configuration.LaunchOnBoot;
398 atomicOperationsToolStripMenuItem.Checked = Configuration.AtomicOperations; 427 atomicOperationsToolStripMenuItem.Checked = Configuration.AtomicOperations;
399 enableToolStripMenuItem.Checked = Configuration.Enabled; 428 enableToolStripMenuItem.Checked = Configuration.Enabled;
400 showBalloonTooltipsToolStripMenuItem.Checked = Configuration.Enabled; 429 showBalloonTooltipsToolStripMenuItem.Checked = Configuration.Enabled;
401 windowToolStripMenuItem.Checked = Configuration.CaptureMode == CaptureMode.Window; 430 windowToolStripMenuItem.Checked = Configuration.CaptureMode == CaptureMode.Window;
402 screenToolStripMenuItem.Checked = Configuration.CaptureMode == CaptureMode.Screen; 431 screenToolStripMenuItem.Checked = Configuration.CaptureMode == CaptureMode.Screen;
403 networkSharingToolStripMenuItem.Checked = Configuration.NetworkSharing; 432 networkSharingToolStripMenuItem.Checked = Configuration.NetworkSharing;
404   433  
405 // Load all tracked folders. 434 // Load all tracked folders.
406 var folders = await LoadFolders(); 435 var folders = await LoadFolders();
407   436  
408 foreach (var folder in folders.Folder) 437 foreach (var folder in folders.Folder)
409 { 438 {
410 TrackedFolders.Folder.Add(folder); 439 TrackedFolders.Folder.Add(folder);
411 } 440 }
412   441  
413 ToggleWatchers(); 442 ToggleWatchers();
414 } 443 }
415   444  
416 private void ShowBalloonTooltipsToolStripMenuItem_CheckedChanged(object sender, EventArgs e) 445 private void ShowBalloonTooltipsToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
417 { 446 {
418 Configuration.ShowBalloonTooltips = ((ToolStripMenuItem)sender).Checked; 447 Configuration.ShowBalloonTooltips = ((ToolStripMenuItem)sender).Checked;
419   448  
420 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1), 449 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
421 async () => { await SaveConfiguration(); }, _cancellationToken); 450 async () => { await SaveConfiguration(); }, _cancellationToken);
422 } 451 }
423   452  
424 private void LaunchOnBootToolStripMenuItem_CheckedChanged(object sender, EventArgs e) 453 private void LaunchOnBootToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
425 { 454 {
426 Configuration.LaunchOnBoot = ((ToolStripMenuItem)sender).Checked; 455 Configuration.LaunchOnBoot = ((ToolStripMenuItem)sender).Checked;
427   456  
428 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1), 457 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
429 async () => { await SaveConfiguration(); }, _cancellationToken); 458 async () => { await SaveConfiguration(); }, _cancellationToken);
430   459  
431 Miscellaneous.LaunchOnBootSet(Configuration.LaunchOnBoot); 460 Miscellaneous.LaunchOnBootSet(Configuration.LaunchOnBoot);
432 } 461 }
433   462  
434 private void AtomicOperationsToolStripMenuItem_CheckedChanged(object sender, EventArgs e) 463 private void AtomicOperationsToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
435 { 464 {
436 Configuration.AtomicOperations = ((ToolStripMenuItem)sender).Checked; 465 Configuration.AtomicOperations = ((ToolStripMenuItem)sender).Checked;
437 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1), 466 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
438 async () => { await SaveConfiguration(); }, _cancellationToken); 467 async () => { await SaveConfiguration(); }, _cancellationToken);
439 } 468 }
440   469  
441 private void TrashDatabaseToolStripMenuItem_Click(object sender, EventArgs e) 470 private void TrashDatabaseToolStripMenuItem_Click(object sender, EventArgs e)
442 { 471 {
443 try 472 try
444 { 473 {
445 File.Delete(Constants.DatabaseFilePath); 474 File.Delete(Constants.DatabaseFilePath);
446   475  
447 if (Configuration.ShowBalloonTooltips) 476 if (Configuration.ShowBalloonTooltips)
448 { 477 {
449 ShowBalloon("Database Deleted", $"Database file {Constants.DatabaseFilePath} has been deleted.", 478 ShowBalloon("Database Deleted", $"Database file {Constants.DatabaseFilePath} has been deleted.",
450 5000); 479 5000);
451 } 480 }
452   481  
453 Log.Information($"Database file {Constants.DatabaseFilePath} has been deleted."); 482 Log.Information($"Database file {Constants.DatabaseFilePath} has been deleted.");
454 } 483 }
455 catch (Exception exception) 484 catch (Exception exception)
456 { 485 {
457 if (Configuration.ShowBalloonTooltips) 486 if (Configuration.ShowBalloonTooltips)
458 { 487 {
459 ShowBalloon("Could not Delete Database", 488 ShowBalloon("Could not Delete Database",
460 $"Database file {Constants.DatabaseFilePath} delete failed with error: {exception.Message}", 489 $"Database file {Constants.DatabaseFilePath} delete failed with error: {exception.Message}",
461 5000); 490 5000);
462 } 491 }
463   492  
464 Log.Information( 493 Log.Information(
465 $"Database file {Constants.DatabaseFilePath} delete failed with error: {exception.Message}"); 494 $"Database file {Constants.DatabaseFilePath} delete failed with error: {exception.Message}");
466 } 495 }
467 } 496 }
468   497  
469 private void EnableToolStripMenuItem_CheckedChanged(object sender, EventArgs e) 498 private void EnableToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
470 { 499 {
471 Configuration.Enabled = enableToolStripMenuItem.Checked; 500 Configuration.Enabled = enableToolStripMenuItem.Checked;
472   501  
473 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1), 502 ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
474 async () => { await SaveConfiguration(); }, _cancellationToken); 503 async () => { await SaveConfiguration(); }, _cancellationToken);
475   504  
476 ToggleWatchers(); 505 ToggleWatchers();
477 } 506 }
478   507  
479 private void SnapshotsToolStripMenuItem_Click(object sender, EventArgs e) 508 private void SnapshotsToolStripMenuItem_Click(object sender, EventArgs e)
480 { 509 {
481 if (_snapshotManagerForm != null) 510 if (_snapshotManagerForm != null)
482 { 511 {
483 return; 512 return;
484 } 513 }
485   514  
486 _snapshotManagerForm = new SnapshotManagerForm(this, _snapshotDatabase, _cancellationToken); 515 _snapshotManagerForm = new SnapshotManagerForm(this, _snapshotDatabase, _cancellationToken);
487 _snapshotManagerForm.Closing += SnapshotManagerFormClosing; 516 _snapshotManagerForm.Closing += SnapshotManagerFormClosing;
488 _snapshotManagerForm.Show(); 517 _snapshotManagerForm.Show();
489 } 518 }
490   519  
491 private void SnapshotManagerFormClosing(object sender, CancelEventArgs e) 520 private void SnapshotManagerFormClosing(object sender, CancelEventArgs e)
492 { 521 {
493 if (_snapshotManagerForm == null) 522 if (_snapshotManagerForm == null)
494 { 523 {
495 return; 524 return;
496 } 525 }
497   526  
498 _snapshotManagerForm.Closing -= SnapshotManagerFormClosing; 527 _snapshotManagerForm.Closing -= SnapshotManagerFormClosing;
499 _snapshotManagerForm.Close(); 528 _snapshotManagerForm.Close();
500 _snapshotManagerForm = null; 529 _snapshotManagerForm = null;
501 } 530 }
502   531  
503 private async void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e) 532 private async void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
504 { 533 {
505 // Ignore directories. 534 // Ignore directories.
506 if (Directory.Exists(e.FullPath)) 535 if (Directory.Exists(e.FullPath))
507 { 536 {
508 return; 537 return;
509 } 538 }
510   539  
511 await _changedFilesLock.WaitAsync(_cancellationToken); 540 await _changedFilesLock.WaitAsync(_cancellationToken);
512 try 541 try
513 { 542 {
514 var delay = global::TrackedFolders.Constants.Delay; 543 var delay = global::TrackedFolders.Constants.Delay;
515 var color = Color.Empty; 544 var color = Color.Empty;
516   545  
517 if (TrackedFolders.TryGet(e.FullPath, out var folder)) 546 if (TrackedFolders.TryGet(e.FullPath, out var folder))
518 { 547 {
519 delay = folder.Delay; 548 delay = folder.Delay;
520 color = folder.Color; 549 color = folder.Color;
521 } 550 }
522   551  
523 if (_changedFiles.Contains(e.FullPath)) 552 if (_changedFiles.Contains(e.FullPath))
524 { 553 {
525 _changedFilesContinuation.Schedule(delay, async () => await TakeSnapshots(color, _cancellationToken), _cancellationToken); 554 _changedFilesContinuation.Schedule(delay, async () => await TakeSnapshots(color, _cancellationToken), _cancellationToken);
526 return; 555 return;
527 } 556 }
528   557  
529 _changedFiles.Add(e.FullPath); 558 _changedFiles.Add(e.FullPath);
530   559  
531 _changedFilesContinuation.Schedule(delay, async () => await TakeSnapshots(color, _cancellationToken), _cancellationToken); 560 _changedFilesContinuation.Schedule(delay, async () => await TakeSnapshots(color, _cancellationToken), _cancellationToken);
532 } 561 }
533 catch (Exception exception) 562 catch (Exception exception)
534 { 563 {
535 Log.Error(exception, "Could not process changed files."); 564 Log.Error(exception, "Could not process changed files.");
536 } 565 }
537 finally 566 finally
538 { 567 {
539 _changedFilesLock.Release(); 568 _changedFilesLock.Release();
540 } 569 }
541 } 570 }
542   571  
543 private void AboutToolStripMenuItem_Click(object sender, EventArgs e) 572 private void AboutToolStripMenuItem_Click(object sender, EventArgs e)
544 { 573 {
545 if (_aboutForm != null) 574 if (_aboutForm != null)
546 { 575 {
547 return; 576 return;
548 } 577 }
549   578  
550 _aboutForm = new AboutForm(_cancellationToken); 579 _aboutForm = new AboutForm(_cancellationToken);
551 _aboutForm.Closing += AboutForm_Closing; 580 _aboutForm.Closing += AboutForm_Closing;
552 _aboutForm.Show(); 581 _aboutForm.Show();
553 } 582 }
554   583  
555 private void AboutForm_Closing(object sender, CancelEventArgs e) 584 private void AboutForm_Closing(object sender, CancelEventArgs e)
556 { 585 {
557 if (_aboutForm == null) 586 if (_aboutForm == null)
558 { 587 {
559 return; 588 return;
560 } 589 }
561   590  
562 _aboutForm.Dispose(); 591 _aboutForm.Dispose();
563 _aboutForm = null; 592 _aboutForm = null;
564 } 593 }
565   594  
566 private void QuitToolStripMenuItem_Click(object sender, EventArgs e) 595 private void QuitToolStripMenuItem_Click(object sender, EventArgs e)
567 { 596 {
568 Close(); 597 Close();
569 } 598 }
570   599  
571 private async void UpdateToolStripMenuItem_Click(object sender, EventArgs e) 600 private async void UpdateToolStripMenuItem_Click(object sender, EventArgs e)
572 { 601 {
573 // Manually check for updates, this will not show a ui 602 // Manually check for updates, this will not show a ui
574 var result = await _sparkle.CheckForUpdatesQuietly(); 603 var result = await _sparkle.CheckForUpdatesQuietly();
575 if (result.Status == UpdateStatus.UpdateAvailable) 604 if (result.Status == UpdateStatus.UpdateAvailable)
576 { 605 {
577 // if update(s) are found, then we have to trigger the UI to show it gracefully 606 // if update(s) are found, then we have to trigger the UI to show it gracefully
578 _sparkle.ShowUpdateNeededUI(); 607 _sparkle.ShowUpdateNeededUI();
579 return; 608 return;
580 } 609 }
581   610  
582 MessageBox.Show("No updates available at this time.", "Horizon", MessageBoxButtons.OK, 611 MessageBox.Show("No updates available at this time.", "Horizon", MessageBoxButtons.OK,
583 MessageBoxIcon.Asterisk, 612 MessageBoxIcon.Asterisk,
584 MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false); 613 MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false);
585 } 614 }
586   615  
587 private void NotifyIcon1_Click(object sender, EventArgs e) 616 private void NotifyIcon1_Click(object sender, EventArgs e)
588 { 617 {
589 if (e is MouseEventArgs mouseEventArgs && mouseEventArgs.Button == MouseButtons.Left) 618 if (e is MouseEventArgs mouseEventArgs && mouseEventArgs.Button == MouseButtons.Left)
590 { 619 {
591 } 620 }
592 } 621 }
593   622  
594 private void ManageFoldersToolStripMenuItem_Click(object sender, EventArgs e) 623 private void ManageFoldersToolStripMenuItem_Click(object sender, EventArgs e)
595 { 624 {
596 if (_manageFoldersForm != null) 625 if (_manageFoldersForm != null)
597 { 626 {
598 return; 627 return;
599 } 628 }
600   629  
601 _manageFoldersForm = new ManageFoldersForm(this, _cancellationToken); 630 _manageFoldersForm = new ManageFoldersForm(this, _cancellationToken);
602 _manageFoldersForm.Closing += ManageFoldersForm_Closing; 631 _manageFoldersForm.Closing += ManageFoldersForm_Closing;
603 _manageFoldersForm.Show(); 632 _manageFoldersForm.Show();
604 } 633 }
605   634  
606 private void ManageFoldersForm_Closing(object sender, CancelEventArgs e) 635 private void ManageFoldersForm_Closing(object sender, CancelEventArgs e)
607 { 636 {
608 if (_manageFoldersForm == null) 637 if (_manageFoldersForm == null)
609 { 638 {
610 return; 639 return;
611 } 640 }
612   641  
613 _manageFoldersForm.Closing -= ManageFoldersForm_Closing; 642 _manageFoldersForm.Closing -= ManageFoldersForm_Closing;
614 _manageFoldersForm.Close(); 643 _manageFoldersForm.Close();
615 _manageFoldersForm = null; 644 _manageFoldersForm = null;
616 } 645 }
617   646  
618 #endregion 647 #endregion
619   648  
620 #region Public Methods 649 #region Public Methods
621   650  
622 public void ShowBalloon(string title, string text, int time) 651 public void ShowBalloon(string title, string text, int time)
623 { 652 {
624 notifyIcon1.BalloonTipTitle = title; 653 notifyIcon1.BalloonTipTitle = title;
625 notifyIcon1.BalloonTipText = text; 654 notifyIcon1.BalloonTipText = text;
626 notifyIcon1.ShowBalloonTip(time); 655 notifyIcon1.ShowBalloonTip(time);
627 } 656 }
628   657  
629 public async Task TakeSnapshot(string path) 658 public async Task TakeSnapshot(string path)
630 { 659 {
631 foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.TopDirectoryOnly)) 660 foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.TopDirectoryOnly))
632 { 661 {
633 try 662 try
634 { 663 {
635 var fileName = Path.GetFileName(file); 664 var fileName = Path.GetFileName(file);
636 var directory = Path.GetDirectoryName(fileName); 665 var directory = Path.GetDirectoryName(fileName);
637 var color = Color.Empty; 666 var color = Color.Empty;
638 if (TrackedFolders.TryGet(directory, out var folder)) 667 if (TrackedFolders.TryGet(directory, out var folder))
639 { 668 {
640 color = folder.Color; 669 color = folder.Color;
641 } 670 }
642   671  
643 await _snapshotDatabase.CreateSnapshotAsync(fileName, file, color, _cancellationToken); 672 await _snapshotDatabase.CreateSnapshotAsync(fileName, file, color, _cancellationToken);
644 } 673 }
645 catch (SQLiteException exception) 674 catch (SQLiteException exception)
646 { 675 {
647 if (exception.ResultCode == SQLiteErrorCode.Constraint) 676 if (exception.ResultCode == SQLiteErrorCode.Constraint)
648 { 677 {
649 Log.Information(exception, "Snapshot already exists."); 678 Log.Information(exception, "Snapshot already exists.");
650 } 679 }
651 } 680 }
652 catch (Exception exception) 681 catch (Exception exception)
653 { 682 {
654 Log.Error(exception, "Could not take snapshot.", file); 683 Log.Error(exception, "Could not take snapshot.", file);
655 } 684 }
656 } 685 }
657 } 686 }
658   687  
659 public async Task TakeSnapshotRecursive(string path) 688 public async Task TakeSnapshotRecursive(string path)
660 { 689 {
661 foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)) 690 foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
662 { 691 {
663 try 692 try
664 { 693 {
665 var fileName = Path.GetFileName(file); 694 var fileName = Path.GetFileName(file);
666 var directory = Path.GetDirectoryName(fileName); 695 var directory = Path.GetDirectoryName(fileName);
667 var color = Color.Empty; 696 var color = Color.Empty;
668 if (TrackedFolders.TryGet(directory, out var folder)) 697 if (TrackedFolders.TryGet(directory, out var folder))
669 { 698 {
670 color = folder.Color; 699 color = folder.Color;
671 } 700 }
672   701  
673 await _snapshotDatabase.CreateSnapshotAsync(fileName, file, color, _cancellationToken); 702 await _snapshotDatabase.CreateSnapshotAsync(fileName, file, color, _cancellationToken);
674 } 703 }
675 catch (SQLiteException exception) 704 catch (SQLiteException exception)
676 { 705 {
677 if (exception.ResultCode == SQLiteErrorCode.Constraint) 706 if (exception.ResultCode == SQLiteErrorCode.Constraint)
678 { 707 {
679 Log.Information(exception, "Snapshot already exists."); 708 Log.Information(exception, "Snapshot already exists.");
680 } 709 }
681 } 710 }
682 catch (Exception exception) 711 catch (Exception exception)
683 { 712 {
684 Log.Error(exception, "Could not take snapshot.", file); 713 Log.Error(exception, "Could not take snapshot.", file);
685 } 714 }
686 } 715 }
687 } 716 }
688   717  
689 public async Task SaveConfiguration() 718 public async Task SaveConfiguration()
690 { 719 {
691 if (!Directory.Exists(Constants.UserApplicationDirectory)) 720 if (!Directory.Exists(Constants.UserApplicationDirectory))
692 { 721 {
693 Directory.CreateDirectory(Constants.UserApplicationDirectory); 722 Directory.CreateDirectory(Constants.UserApplicationDirectory);
694 } 723 }
695   724  
696 switch (await Serialization.Serialize(Configuration, Constants.ConfigurationFile, "Configuration", 725 switch (await Serialization.Serialize(Configuration, Constants.ConfigurationFile, "Configuration",
697 "<!ATTLIST Configuration xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>", 726 "<!ATTLIST Configuration xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
698 CancellationToken.None)) 727 CancellationToken.None))
699 { 728 {
700 case SerializationSuccess<Configuration.Configuration> _: 729 case SerializationSuccess<Configuration.Configuration> _:
701 Log.Information("Serialized configuration."); 730 Log.Information("Serialized configuration.");
702 break; 731 break;
703 case SerializationFailure serializationFailure: 732 case SerializationFailure serializationFailure:
704 Log.Warning(serializationFailure.Exception.Message, "Failed to serialize configuration."); 733 Log.Warning(serializationFailure.Exception.Message, "Failed to serialize configuration.");
705 break; 734 break;
706 } 735 }
707 } 736 }
708   737  
709 public static async Task<Configuration.Configuration> LoadConfiguration() 738 public static async Task<Configuration.Configuration> LoadConfiguration()
710 { 739 {
711 if (!Directory.Exists(Constants.UserApplicationDirectory)) 740 if (!Directory.Exists(Constants.UserApplicationDirectory))
712 { 741 {
713 Directory.CreateDirectory(Constants.UserApplicationDirectory); 742 Directory.CreateDirectory(Constants.UserApplicationDirectory);
714 } 743 }
715   744  
716 var deserializationResult = 745 var deserializationResult =
717 await Serialization.Deserialize<Configuration.Configuration>(Constants.ConfigurationFile, 746 await Serialization.Deserialize<Configuration.Configuration>(Constants.ConfigurationFile,
718 Constants.ConfigurationNamespace, Constants.ConfigurationXsd, CancellationToken.None); 747 Constants.ConfigurationNamespace, Constants.ConfigurationXsd, CancellationToken.None);
719   748  
720 switch (deserializationResult) 749 switch (deserializationResult)
721 { 750 {
722 case SerializationSuccess<Configuration.Configuration> serializationSuccess: 751 case SerializationSuccess<Configuration.Configuration> serializationSuccess:
723 return serializationSuccess.Result; 752 return serializationSuccess.Result;
724 case SerializationFailure serializationFailure: 753 case SerializationFailure serializationFailure:
725 Log.Warning(serializationFailure.Exception, "Failed to load configuration."); 754 Log.Warning(serializationFailure.Exception, "Failed to load configuration.");
726 return new Configuration.Configuration(); 755 return new Configuration.Configuration();
727 default: 756 default:
728 return new Configuration.Configuration(); 757 return new Configuration.Configuration();
729 } 758 }
730 } 759 }
731   760  
732 public static async Task<TrackedFolders.TrackedFolders> LoadFolders() 761 public static async Task<TrackedFolders.TrackedFolders> LoadFolders()
733 { 762 {
734 if (!Directory.Exists(Constants.UserApplicationDirectory)) 763 if (!Directory.Exists(Constants.UserApplicationDirectory))
735 { 764 {
736 Directory.CreateDirectory(Constants.UserApplicationDirectory); 765 Directory.CreateDirectory(Constants.UserApplicationDirectory);
737 } 766 }
738   767  
739 var deserializationResult = 768 var deserializationResult =
740 await Serialization.Deserialize<TrackedFolders.TrackedFolders>(Constants.FoldersFile, 769 await Serialization.Deserialize<TrackedFolders.TrackedFolders>(Constants.FoldersFile,
741 Constants.TrackedFoldersNamespace, Constants.TrackedFoldersXsd, CancellationToken.None); 770 Constants.TrackedFoldersNamespace, Constants.TrackedFoldersXsd, CancellationToken.None);
742   771  
743 switch (deserializationResult) 772 switch (deserializationResult)
744 { 773 {
745 case SerializationSuccess<TrackedFolders.TrackedFolders> serializationSuccess: 774 case SerializationSuccess<TrackedFolders.TrackedFolders> serializationSuccess:
746 return serializationSuccess.Result; 775 return serializationSuccess.Result;
747 case SerializationFailure serializationFailure: 776 case SerializationFailure serializationFailure:
748 Log.Warning(serializationFailure.Exception, "Failed to load tracked folders"); 777 Log.Warning(serializationFailure.Exception, "Failed to load tracked folders");
749 return new TrackedFolders.TrackedFolders(); 778 return new TrackedFolders.TrackedFolders();
750 default: 779 default:
751 return new TrackedFolders.TrackedFolders(); 780 return new TrackedFolders.TrackedFolders();
752 } 781 }
753 } 782 }
754   783  
755 public async Task SaveFolders() 784 public async Task SaveFolders()
756 { 785 {
757 if (!Directory.Exists(Constants.UserApplicationDirectory)) 786 if (!Directory.Exists(Constants.UserApplicationDirectory))
758 { 787 {
759 Directory.CreateDirectory(Constants.UserApplicationDirectory); 788 Directory.CreateDirectory(Constants.UserApplicationDirectory);
760 } 789 }
761   790  
762 switch (await Serialization.Serialize(TrackedFolders, Constants.FoldersFile, "TrackedFolders", 791 switch (await Serialization.Serialize(TrackedFolders, Constants.FoldersFile, "TrackedFolders",
763 "<!ATTLIST TrackedFolders xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>", 792 "<!ATTLIST TrackedFolders xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
764 CancellationToken.None)) 793 CancellationToken.None))
765 { 794 {
766 case SerializationSuccess<TrackedFolders.TrackedFolders> _: 795 case SerializationSuccess<TrackedFolders.TrackedFolders> _:
767 Log.Information("Serialized tracked folders."); 796 Log.Information("Serialized tracked folders.");
768 break; 797 break;
769 case SerializationFailure serializationFailure: 798 case SerializationFailure serializationFailure:
770 Log.Warning(serializationFailure.Exception.Message, "Failed to serialize tracked folders."); 799 Log.Warning(serializationFailure.Exception.Message, "Failed to serialize tracked folders.");
771 break; 800 break;
772 } 801 }
773 } 802 }
774   803  
775 #endregion 804 #endregion
776   805  
777 #region Private Methods 806 #region Private Methods
778   807  
779 private void RemoveWatcher(string folder) 808 private void RemoveWatcher(string folder)
780 { 809 {
781 var removeList = new List<FileSystemWatcher>(); 810 var removeList = new List<FileSystemWatcher>();
782 foreach (var fileSystemWatcher in FileSystemWatchers) 811 foreach (var fileSystemWatcher in FileSystemWatchers)
783 { 812 {
784 if (fileSystemWatcher.Path.IsPathEqual(folder) || 813 if (fileSystemWatcher.Path.IsPathEqual(folder) ||
785 fileSystemWatcher.Path.IsSubPathOf(folder)) 814 fileSystemWatcher.Path.IsSubPathOf(folder))
786 { 815 {
787 removeList.Add(fileSystemWatcher); 816 removeList.Add(fileSystemWatcher);
788 } 817 }
789 } 818 }
790   819  
791 foreach (var fileSystemWatcher in removeList) 820 foreach (var fileSystemWatcher in removeList)
792 { 821 {
793 FileSystemWatchers.Remove(fileSystemWatcher); 822 FileSystemWatchers.Remove(fileSystemWatcher);
794 fileSystemWatcher.Changed -= FileSystemWatcher_Changed; 823 fileSystemWatcher.Changed -= FileSystemWatcher_Changed;
795 fileSystemWatcher.Dispose(); 824 fileSystemWatcher.Dispose();
796 } 825 }
797 } 826 }
798   827  
799 private void AddWatcher(string folder, bool recursive) 828 private void AddWatcher(string folder, bool recursive)
800 { 829 {
801 var fileSystemWatcher = new FileSystemWatcher 830 var fileSystemWatcher = new FileSystemWatcher
802 { 831 {
803 IncludeSubdirectories = recursive, 832 IncludeSubdirectories = recursive,
804 NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Attributes, 833 NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Attributes,
805 Path = folder, 834 Path = folder,
806 EnableRaisingEvents = true 835 EnableRaisingEvents = true
807 }; 836 };
808   837  
809 fileSystemWatcher.Changed += FileSystemWatcher_Changed; 838 fileSystemWatcher.Changed += FileSystemWatcher_Changed;
810   839  
811 FileSystemWatchers.Add(fileSystemWatcher); 840 FileSystemWatchers.Add(fileSystemWatcher);
812 } 841 }
813   842  
814 private void ToggleWatchers() 843 private void ToggleWatchers()
815 { 844 {
816 switch (Configuration.Enabled) 845 switch (Configuration.Enabled)
817 { 846 {
818 case true: 847 case true:
819 foreach (var watcher in FileSystemWatchers) 848 foreach (var watcher in FileSystemWatchers)
820 { 849 {
821 watcher.EnableRaisingEvents = true; 850 watcher.EnableRaisingEvents = true;
822 } 851 }
823   852  
824 if (Configuration.ShowBalloonTooltips) 853 if (Configuration.ShowBalloonTooltips)
825 { 854 {
826 ShowBalloon("Watching", "Watching folders...", 5000); 855 ShowBalloon("Watching", "Watching folders...", 5000);
827 } 856 }
828   857  
829 Log.Information("Watching folders."); 858 Log.Information("Watching folders.");
830   859  
831 break; 860 break;
832 default: 861 default:
833 foreach (var watcher in FileSystemWatchers) 862 foreach (var watcher in FileSystemWatchers)
834 { 863 {
835 watcher.EnableRaisingEvents = false; 864 watcher.EnableRaisingEvents = false;
836 } 865 }
837   866  
838 if (Configuration.ShowBalloonTooltips) 867 if (Configuration.ShowBalloonTooltips)
839 { 868 {
840 ShowBalloon("Not Watching", "Folders are not being watched.", 5000); 869 ShowBalloon("Not Watching", "Folders are not being watched.", 5000);
841 } 870 }
842   871  
843 Log.Information("Folders are not being watched."); 872 Log.Information("Folders are not being watched.");
844   873  
845 break; 874 break;
846 } 875 }
847 } 876 }
848   877  
849 private async Task TakeSnapshots(Color color, CancellationToken cancellationToken) 878 private async Task TakeSnapshots(Color color, CancellationToken cancellationToken)
850 { 879 {
851 var bufferBlock = new BufferBlock<string>(new DataflowBlockOptions() {CancellationToken = cancellationToken}); 880 var bufferBlock = new BufferBlock<string>(new DataflowBlockOptions() {CancellationToken = cancellationToken});
852 var actionBlock = new ActionBlock<string>(async path => 881 var actionBlock = new ActionBlock<string>(async path =>
853 { 882 {
854 // In case files have vanished strictly due to the time specified by the tracked folders delay. 883 // In case files have vanished strictly due to the time specified by the tracked folders delay.
855 if (!File.Exists(path)) 884 if (!File.Exists(path))
856 { 885 {
857 Log.Warning("File vanished after tracked folder delay.", path); 886 Log.Warning("File vanished after tracked folder delay.", path);
858   887  
859 return; 888 return;
860 } 889 }
861   890  
862 try 891 try
863 { 892 {
864 var fileName = System.IO.Path.GetFileName(path); 893 var fileName = System.IO.Path.GetFileName(path);
865 var screenCapture = ScreenCapture.Capture((Utilities.CaptureMode)Configuration.CaptureMode); 894 var screenCapture = ScreenCapture.Capture((Utilities.CaptureMode)Configuration.CaptureMode);
866   895  
867 await _snapshotDatabase.CreateSnapshotAsync(fileName, path, screenCapture, color, 896 await _snapshotDatabase.CreateSnapshotAsync(fileName, path, screenCapture, color,
868 _cancellationToken); 897 _cancellationToken);
869 } 898 }
870 catch (SQLiteException exception) 899 catch (SQLiteException exception)
871 { 900 {
872 if (exception.ResultCode == SQLiteErrorCode.Constraint) 901 if (exception.ResultCode == SQLiteErrorCode.Constraint)
873 { 902 {
874 Log.Information(exception, "Snapshot already exists."); 903 Log.Information(exception, "Snapshot already exists.");
875 } 904 }
876 } 905 }
877 catch (Exception exception) 906 catch (Exception exception)
878 { 907 {
879 Log.Error(exception, "Could not take snapshot.", path); 908 Log.Error(exception, "Could not take snapshot.", path);
880 } 909 }
881 }); 910 });
882   911  
883 using (var snapshotLink = 912 using (var snapshotLink =
884 bufferBlock.LinkTo(actionBlock, new DataflowLinkOptions() { PropagateCompletion = true })) 913 bufferBlock.LinkTo(actionBlock, new DataflowLinkOptions() { PropagateCompletion = true }))
885 { 914 {
886 await _changedFilesLock.WaitAsync(_cancellationToken); 915 await _changedFilesLock.WaitAsync(_cancellationToken);
887 try 916 try
888 { 917 {
889 foreach (var path in _changedFiles) 918 foreach (var path in _changedFiles)
890 { 919 {
891 await bufferBlock.SendAsync(path, cancellationToken); 920 await bufferBlock.SendAsync(path, cancellationToken);
892 } 921 }
893 bufferBlock.Complete(); 922 bufferBlock.Complete();
894 await bufferBlock.Completion; 923 await bufferBlock.Completion;
895 } 924 }
896 catch (Exception exception) 925 catch (Exception exception)
897 { 926 {
898 Log.Error(exception, "Could not take snapshots."); 927 Log.Error(exception, "Could not take snapshots.");
899 } 928 }
900 finally 929 finally
901 { 930 {
902 _changedFiles.Clear(); 931 _changedFiles.Clear();
903 _changedFilesLock.Release(); 932 _changedFilesLock.Release();
904 } 933 }
905 } 934 }
906 } 935 }
907   936  
908 #endregion 937 #endregion
909 } 938 }
910 } 939 }
911   940  
912
Generated by GNU Enscript 1.6.5.90.
941
Generated by GNU Enscript 1.6.5.90.
913   942  
914   943  
915   944