Horizon – Diff between revs 12 and 20

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