Horizon – Blame information for rev 11

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