Horizon – Blame information for rev 21

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