Horizon – Diff between revs 5 and 6

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