Horizon – Blame information for rev 1

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