Horizon – Blame information for rev 5

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