Horizon – Blame information for rev 28

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