Horizon – Blame information for rev 36

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