Horizon – Blame information for rev 15

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 using System;
10 office 2 using System.Collections.Concurrent;
1 office 3 using System.Collections.Generic;
4 using System.Collections.Specialized;
5 using System.ComponentModel;
6 using System.Data.SQLite;
7 using System.Diagnostics;
8 using System.Drawing;
9 using System.IO;
10 using System.Linq;
11 using System.Net;
7 office 12 using System.Net.Http;
13 using System.Net.Http.Headers;
4 office 14 using System.Net.NetworkInformation;
1 office 15 using System.Reflection;
16 using System.Threading;
17 using System.Threading.Tasks;
18 using System.Threading.Tasks.Dataflow;
4 office 19 using System.Windows;
1 office 20 using System.Windows.Forms;
7 office 21 using Configuration;
1 office 22 using Horizon.Database;
7 office 23 using Horizon.Notifications.Gotify;
1 office 24 using Horizon.Snapshots;
25 using Horizon.Utilities;
26 using Horizon.Utilities.Serialization;
27 using Mono.Zeroconf;
28 using NetSparkleUpdater;
29 using NetSparkleUpdater.Enums;
30 using NetSparkleUpdater.SignatureVerifiers;
31 using NetSparkleUpdater.UI.WinForms;
32 using Newtonsoft.Json;
33 using Serilog;
34 using TrackedFolders;
35 using WatsonTcp;
12 office 36 using Newtonsoft;
1 office 37 using static Horizon.Utilities.Networking.Miscellaneous;
38 using CaptureMode = Configuration.CaptureMode;
39 using Path = System.IO.Path;
12 office 40 using System.Text;
13 office 41 using Tesseract;
42 using System.Runtime.CompilerServices;
43 using System.Text.RegularExpressions;
15 office 44 using Horizon.Searching;
1 office 45  
46 namespace Horizon
47 {
48 public partial class MainForm : Form
49 {
50  
51 #region Static Fields and Constants
52  
53 private static SemaphoreSlim _changedFilesLock;
54  
55 private static HashSet<string> _changedFiles;
56  
57 private static ScheduledContinuation _changedFilesContinuation;
58  
4 office 59 private static readonly LogMemorySink _memorySink = new LogMemorySink();
60  
1 office 61 #endregion
62  
63 #region Private Delegates, Events, Enums, Properties, Indexers and Fields
64  
10 office 65 private ScheduledContinuation _changedConfigurationContinuation;
66  
67 private ScheduledContinuation _trackedFoldersChangedContinuation;
68  
69 private Configuration.Configuration _configuration;
70  
71 private TrackedFolders.TrackedFolders _trackedFolders;
72  
73 private readonly ConcurrentQueue<FileSystemWatcher> _fileSystemWatchers;
74  
1 office 75 private readonly CancellationToken _cancellationToken;
76  
10 office 77 private static HttpClient _httpClient;
7 office 78  
1 office 79 private readonly CancellationTokenSource _cancellationTokenSource;
80  
81 private AboutForm _aboutForm;
82  
83 private ManageFoldersForm _manageFoldersForm;
84  
85 private readonly SnapshotDatabase _snapshotDatabase;
86  
87 private SnapshotManagerForm _snapshotManagerForm;
88  
89 private readonly SparkleUpdater _sparkle;
90  
91 private LogViewForm _logViewForm;
92  
12 office 93 private static JsonSerializer _jsonSerializer;
94  
1 office 95 private RegisterService _horizonDiscoveryService;
96  
97 private WatsonTcpServer _horizonNetworkShare;
98  
99 private NotifyFilters _fileSystemWatchersNotifyFilters = NotifyFilters.LastWrite | NotifyFilters.Attributes;
100  
13 office 101 private readonly bool _memorySinkEnabled = true;
1 office 102  
15 office 103 private SearchEngine _searchEngine;
104  
1 office 105 #endregion
106  
107 #region Constructors, Destructors and Finalizers
10 office 108 public MainForm()
109 {
110 InitializeComponent();
1 office 111  
10 office 112 _cancellationTokenSource = new CancellationTokenSource();
113 _cancellationToken = _cancellationTokenSource.Token;
114  
13 office 115 _jsonSerializer = new JsonSerializer();
116  
10 office 117 _httpClient = new HttpClient();
118  
119 _trackedFoldersChangedContinuation = new ScheduledContinuation();
120  
121 _changedFilesLock = new SemaphoreSlim(1, 1);
122 _fileSystemWatchers = new ConcurrentQueue<FileSystemWatcher>();
123  
124 _changedFiles = new HashSet<string>();
125 _changedFilesContinuation = new ScheduledContinuation();
126  
127 _changedConfigurationContinuation = new ScheduledContinuation();
13 office 128  
10 office 129 }
130  
1 office 131 public MainForm(Mutex mutex) : this()
132 {
133 Log.Logger = new LoggerConfiguration()
134 .MinimumLevel.Debug()
13 office 135 .WriteTo.Conditional(condition => _memorySinkEnabled, configureSink => configureSink.Sink(_memorySink))
1 office 136 .WriteTo.File(Path.Combine(Constants.UserApplicationDirectory, "Logs", $"{Constants.AssemblyName}.log"),
137 rollingInterval: RollingInterval.Day)
138 .CreateLogger();
139  
140 _snapshotDatabase = new SnapshotDatabase(_cancellationToken);
141 _snapshotDatabase.SnapshotRevert += SnapshotDatabase_SnapshotRevert;
7 office 142 _snapshotDatabase.SnapshotCreate += SnapshotDatabase_SnapshotCreateAsync;
1 office 143 _snapshotDatabase.SnapshotTransferReceived += SnapshotDatabase_SnapshotTransferReceived;
7 office 144 _snapshotDatabase.SnapshotNoteUpdate += _snapshotDatabase_SnapshotNoteUpdate;
145 _snapshotDatabase.SnapshotDataUpdate += _snapshotDatabase_SnapshotDataUpdate;
1 office 146  
10 office 147 _trackedFolders = new TrackedFolders.TrackedFolders();
148 _trackedFolders.Folder.CollectionChanged += Folder_CollectionChanged;
1 office 149  
150 // Start application update.
151 var manifestModuleName = Assembly.GetEntryAssembly().ManifestModule.FullyQualifiedName;
152 var icon = Icon.ExtractAssociatedIcon(manifestModuleName);
153  
154 _sparkle = new SparkleUpdater("https://horizon.grimore.org/update/appcast.xml",
155 new Ed25519Checker(SecurityMode.Strict, "LonrgxVjSF0GnY4hzwlRJnLkaxnDn2ikdmOifILzLJY="))
156 {
157 UIFactory = new UIFactory(icon),
8 office 158 RelaunchAfterUpdate = true
1 office 159 };
9 office 160  
1 office 161 _sparkle.StartLoop(true, true);
162 }
163  
164 /// <summary>
165 /// Clean up any resources being used.
166 /// </summary>
167 /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
168 protected override void Dispose(bool disposing)
169 {
170 if (disposing && components != null)
171 {
172 toolStripMenuItem3.DropDown.Closing -= toolStropMenuItem3DropDown_Closing;
7 office 173 eventsToolStripMenuItem.DropDown.Closing -= eventsToolStripMenuItem_Closing;
174  
1 office 175 components.Dispose();
176 }
177  
178 _cancellationTokenSource.Cancel();
10 office 179 _configuration.PropertyChanged -= _configuration_PropertyChanged;
1 office 180  
7 office 181 _snapshotDatabase.SnapshotNoteUpdate -= _snapshotDatabase_SnapshotNoteUpdate;
182 _snapshotDatabase.SnapshotDataUpdate -= _snapshotDatabase_SnapshotDataUpdate;
1 office 183 _snapshotDatabase.SnapshotRevert -= SnapshotDatabase_SnapshotRevert;
7 office 184 _snapshotDatabase.SnapshotCreate -= SnapshotDatabase_SnapshotCreateAsync;
1 office 185 _snapshotDatabase.SnapshotTransferReceived -= SnapshotDatabase_SnapshotTransferReceived;
186  
187 _snapshotDatabase.Dispose();
188  
189 base.Dispose(disposing);
190 }
191  
7 office 192 private void eventsToolStripMenuItem_Closing(object sender, ToolStripDropDownClosingEventArgs e)
193 {
194 if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked)
195 {
196 e.Cancel = true;
197 }
198 }
199  
1 office 200 private void toolStropMenuItem3DropDown_Closing(object sender, ToolStripDropDownClosingEventArgs e)
201 {
202 if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked)
203 {
204 e.Cancel = true;
205 }
206 }
207  
208 #endregion
209  
210 #region Event Handlers
7 office 211  
13 office 212 private void autoNotesToolStripMenuItem_Click(object sender, EventArgs e)
213 {
214 _configuration.AutoNotes = ((ToolStripMenuItem)sender).Checked;
215 }
216  
7 office 217 private void gotfyToolStripTextBox_TextChanged(object sender, EventArgs e)
218 {
219 var toolStripTextBox = ((ToolStripTextBox)sender);
220  
10 office 221 _configuration.GotifyURL = toolStripTextBox.Text;
7 office 222 }
223  
224 private void eventsToolStripMenuItem_CheckStateChanged(object sender, EventArgs e)
225 {
226 var toolStripMenuItem = (ToolStripMenuItem)sender;
227  
228 var text = toolStripMenuItem.Text;
229 var state = toolStripMenuItem.CheckState;
230  
231 foreach (var flag in Enum.GetNames(typeof(NotifyEvent)))
232 {
233 if (string.Equals(flag, text, StringComparison.OrdinalIgnoreCase))
234 {
235 if (Enum.TryParse<NotifyEvent>(flag, true, out var setting))
236 {
237 switch (state)
238 {
239 case CheckState.Checked:
10 office 240 _configuration.NotifyEvents = _configuration.NotifyEvents | setting;
7 office 241 break;
242 case CheckState.Unchecked:
10 office 243 _configuration.NotifyEvents = _configuration.NotifyEvents & ~setting;
7 office 244 break;
245 }
246  
247 }
248 }
249 }
250 }
251  
252 private void gotifyToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
253 {
10 office 254 _configuration.EnableGotify = ((ToolStripMenuItem)sender).Checked;
7 office 255 }
256  
1 office 257 private void attributesToolStripMenuItem_CheckStateChanged(object sender, EventArgs e)
258 {
259 var toolStripMenuItem = (ToolStripMenuItem)sender;
260  
261 var text = toolStripMenuItem.Text;
262 var state = toolStripMenuItem.CheckState;
263  
264 foreach (var flag in Enum.GetNames(typeof(NotifyFilters)))
265 {
266 if (string.Equals(flag, text, StringComparison.OrdinalIgnoreCase))
267 {
268 if (Enum.TryParse<NotifyFilters>(flag, true, out var setting))
269 {
270 switch (state)
271 {
272 case CheckState.Checked:
273 _fileSystemWatchersNotifyFilters = _fileSystemWatchersNotifyFilters | setting;
274 break;
275 case CheckState.Unchecked:
276 _fileSystemWatchersNotifyFilters = _fileSystemWatchersNotifyFilters & ~setting;
277 break;
278 }
279  
280 }
281 }
282 }
283  
10 office 284 _configuration.NotifyFilters = _fileSystemWatchersNotifyFilters;
1 office 285 }
286  
287 private void networkSharingToolStripMenuItem_CheckStateChanged(object sender, EventArgs e)
288 {
289 var toolStripMenuItem = (ToolStripMenuItem)sender;
290  
291 switch (toolStripMenuItem.CheckState)
292 {
293 case CheckState.Checked:
294 var freePort = GetAvailableTcpPort();
295  
296 _horizonNetworkShare = new WatsonTcpServer("0.0.0.0", freePort);
297 _horizonNetworkShare.Events.ClientConnected += Events_ClientConnected;
298 _horizonNetworkShare.Events.ClientDisconnected += Events_ClientDisconnected;
299 _horizonNetworkShare.Events.MessageReceived += Events_MessageReceived;
300 _horizonNetworkShare.Events.ExceptionEncountered += Events_ExceptionEncountered;
301 #pragma warning disable CS4014
302 _horizonNetworkShare.Start();
303 #pragma warning restore CS4014
304  
305 try
306 {
307 _horizonDiscoveryService = new RegisterService();
308 _horizonDiscoveryService.Name = $"Horizon ({Environment.MachineName})";
309 _horizonDiscoveryService.RegType = "_horizon._tcp";
310 _horizonDiscoveryService.ReplyDomain = "local.";
311 _horizonDiscoveryService.UPort = freePort;
312 _horizonDiscoveryService.Register();
313 }
314 catch (Exception exception)
315 {
316 Log.Error(exception, "Service discovery protocol could not be stared.");
317 }
318  
10 office 319 _configuration.NetworkSharing = true;
1 office 320 break;
321 case CheckState.Unchecked:
322 if (_horizonNetworkShare != null)
323 {
324 _horizonNetworkShare.Events.ClientConnected -= Events_ClientConnected;
325 _horizonNetworkShare.Events.ClientDisconnected -= Events_ClientDisconnected;
326 _horizonNetworkShare.Events.MessageReceived -= Events_MessageReceived;
327 _horizonNetworkShare.Events.ExceptionEncountered -= Events_ExceptionEncountered;
328  
329 _horizonNetworkShare.Dispose();
330 _horizonNetworkShare = null;
331 }
332  
333 if (_horizonDiscoveryService != null)
334 {
335 _horizonDiscoveryService.Dispose();
336 _horizonDiscoveryService = null;
337  
338 }
339  
10 office 340 _configuration.NetworkSharing = false;
1 office 341 break;
342 }
343 }
344  
345 private void Events_ExceptionEncountered(object sender, ExceptionEventArgs e)
346 {
347 Log.Error(e.Exception,$"Client threw exception.");
348 }
349  
350 private async void Events_MessageReceived(object sender, MessageReceivedEventArgs e)
351 {
352 Log.Information($"Client {e.Client?.IpPort} sent {e.Data?.Length} bytes via network sharing.");
353  
354 if (e.Data?.Length == 0)
355 {
356 return;
357 }
358  
359 try
360 {
12 office 361 //var payload = Encoding.UTF8.GetString();
1 office 362  
12 office 363 using var memoryStream = new MemoryStream(e.Data);
364 using var streamReader = new StreamReader(memoryStream);
365 using var jsonTextReader = new JsonTextReader(streamReader);
15 office 366 var completeSnapshot = _jsonSerializer.Deserialize<Snapshot>(jsonTextReader);
1 office 367  
15 office 368 await _snapshotDatabase.ApplySnapshotAsync(completeSnapshot, _cancellationToken);
1 office 369  
370 Log.Information($"Stored {completeSnapshot.Name} from {e.Client?.IpPort}");
371 }
372 catch (Exception exception)
373 {
374 Log.Error(exception, $"Failed to process network share from {e.Client?.IpPort}.");
375 }
376 }
377  
378 private void Events_ClientDisconnected(object sender, WatsonTcp.DisconnectionEventArgs e)
379 {
380 Log.Information($"Client {e.Client?.IpPort} disconnected from network sharing.");
381 }
382  
383 private void Events_ClientConnected(object sender, WatsonTcp.ConnectionEventArgs e)
384 {
385 Log.Information($"Client {e.Client?.IpPort} connected to network sharing.");
386 }
387  
388 private void WindowToolStripMenuItem_Click(object sender, EventArgs e)
389 {
390 windowToolStripMenuItem.Checked = true;
391 screenToolStripMenuItem.Checked = false;
392  
10 office 393 _configuration.CaptureMode = CaptureMode.Window;
1 office 394 }
395  
396 private void ScreenToolStripMenuItem_Click(object sender, EventArgs e)
397 {
398 screenToolStripMenuItem.Checked = true;
399 windowToolStripMenuItem.Checked = false;
400  
10 office 401 _configuration.CaptureMode = CaptureMode.Screen;
1 office 402 }
403  
404 private void LogViewToolStripMenuItem_Click(object sender, EventArgs e)
405 {
406 if (_logViewForm != null)
407 {
408 return;
409 }
410  
411 _logViewForm = new LogViewForm(this, _memorySink, _cancellationToken);
412 _logViewForm.Closing += LogViewFormClosing;
413 _logViewForm.Show();
414 }
415  
416 private void LogViewFormClosing(object sender, CancelEventArgs e)
417 {
418 if (_logViewForm == null)
419 {
420 return;
421 }
422  
423 _logViewForm.Closing -= LogViewFormClosing;
424 _logViewForm.Close();
425 _logViewForm = null;
426 }
427  
7 office 428 private async void SnapshotDatabase_SnapshotCreateAsync(object sender, SnapshotCreateEventArgs e)
1 office 429 {
430 switch (e)
431 {
432 case SnapshotCreateSuccessEventArgs snapshotCreateSuccessEventArgs:
13 office 433 var snapshot = snapshotCreateSuccessEventArgs.Snapshot;
10 office 434 if (_configuration.ShowBalloonTooltips)
1 office 435 {
13 office 436 ShowBalloon("Snapshot Created", $"Took a snapshot of {snapshot.Path}.",
1 office 437 5000);
438 }
439  
10 office 440 if(_configuration.EnableGotify && _configuration.NotifyEvents.HasFlag(NotifyEvent.Create))
7 office 441 {
13 office 442 await SendGotifyNotification("Snapshot Created", $"Took a snapshot of {snapshot.Name}.");
7 office 443 }
444  
13 office 445 Log.Information($"Took a snapshot of {snapshot.Path}.");
1 office 446  
447 break;
448 case SnapshotCreateFailureEventArgs snapshotCreateFailureEventArgs:
10 office 449 if (_configuration.ShowBalloonTooltips)
1 office 450 {
451 ShowBalloon("Snapshot Failed",
452 $"Failed to take a snapshot of {snapshotCreateFailureEventArgs.Path}.", 5000);
453 }
454  
455 Log.Information($"Failed to take a snapshot of {snapshotCreateFailureEventArgs.Path}.");
456  
457 break;
458 }
459 }
7 office 460 private async void _snapshotDatabase_SnapshotDataUpdate(object sender, SnapshotDataUpdateEventArgs e)
461 {
462 switch(e)
463 {
464 case SnapshotDataUpdateSuccessEventArgs snapshotDataUpdateSuccessEventArgs:
10 office 465 if (_configuration.EnableGotify && _configuration.NotifyEvents.HasFlag(NotifyEvent.Update))
7 office 466 {
467 await SendGotifyNotification("Snapshot Updated", $"Snapshot data updated {snapshotDataUpdateSuccessEventArgs.NewHash} from {snapshotDataUpdateSuccessEventArgs.OldHash}.");
468 }
469 break;
470 }
471 }
1 office 472  
7 office 473 private async void _snapshotDatabase_SnapshotNoteUpdate(object sender, SnapshotNoteUpdateEventArgs e)
1 office 474 {
7 office 475 switch(e)
476 {
477 case SnapshotNoteUpdateSuccessEventArgs snapshotNoteUpdateSuccessEventArgs:
10 office 478 if (_configuration.EnableGotify && _configuration.NotifyEvents.HasFlag(NotifyEvent.Update))
7 office 479 {
480 await SendGotifyNotification("Snapshot Updated", $"Snapshot note updated for {snapshotNoteUpdateSuccessEventArgs.Hash}.");
481 }
482 break;
483 }
484  
485 }
486  
487 private async void SnapshotDatabase_SnapshotTransferReceived(object sender, SnapshotCreateEventArgs e)
488 {
1 office 489 switch (e)
490 {
491 case SnapshotCreateSuccessEventArgs snapshotCreateSuccessEventArgs:
13 office 492 var snapshot = snapshotCreateSuccessEventArgs.Snapshot;
10 office 493 if (_configuration.ShowBalloonTooltips)
1 office 494 {
13 office 495 ShowBalloon("Snapshot Transfer Success", $"A snapshot has been transferred {snapshot.Path}.",
1 office 496 5000);
497 }
498  
10 office 499 if (_configuration.EnableGotify && _configuration.NotifyEvents.HasFlag(NotifyEvent.Transfer))
7 office 500 {
13 office 501 await SendGotifyNotification("Snapshot Transerred", $"A snapshot has been transferred {snapshot.Name}.");
7 office 502 }
503  
13 office 504 Log.Information($"A snapshot transfer succeeded {snapshot.Path}.");
1 office 505  
506 break;
507 case SnapshotCreateFailureEventArgs snapshotCreateFailureEventArgs:
10 office 508 if (_configuration.ShowBalloonTooltips)
1 office 509 {
510 ShowBalloon("Snapshot Transfer Failure",
511 $"A snapshot failed to transfer {snapshotCreateFailureEventArgs.Path}.", 5000);
512 }
513  
514 Log.Information($"A snapshot failed to transfer {snapshotCreateFailureEventArgs.Path}.");
515  
516 break;
517 }
518 }
519  
7 office 520 private async void SnapshotDatabase_SnapshotRevert(object sender, SnapshotRevertEventArgs e)
1 office 521 {
522 switch (e)
523 {
524 case SnapshotRevertSuccessEventArgs snapshotRevertSuccessEventArgs:
10 office 525 if (_configuration.ShowBalloonTooltips)
1 office 526 {
527 ShowBalloon("Revert Succeeded", $"File {snapshotRevertSuccessEventArgs.Name} reverted.", 5000);
528 }
529  
10 office 530 if (_configuration.EnableGotify && _configuration.NotifyEvents.HasFlag(NotifyEvent.Revert))
7 office 531 {
532 await SendGotifyNotification("Snapshot Reverted", $"Reverted a snapshot of {snapshotRevertSuccessEventArgs.Name}.");
533 }
534  
535  
1 office 536 Log.Information($"File {snapshotRevertSuccessEventArgs.Name} reverted.");
537  
538 break;
539 case SnapshotRevertFailureEventArgs snapshotRevertFailureEventArgs:
10 office 540 if (_configuration.ShowBalloonTooltips)
1 office 541 {
542 ShowBalloon("Revert Failed", $"Reverting file {snapshotRevertFailureEventArgs.Name} failed.",
543 5000);
544 }
545  
546 Log.Information($"Reverting file {snapshotRevertFailureEventArgs.Name} failed.");
547  
548 break;
549 }
550 }
551  
10 office 552 private void Folder_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
1 office 553 {
554 if (e.OldItems != null)
555 {
556 foreach (var item in e.OldItems.OfType<Folder>())
557 {
558 RemoveWatcher(item.Path);
559 }
560 }
561  
562 if (e.NewItems != null)
563 {
564 foreach (var item in e.NewItems.OfType<Folder>())
565 {
566 // If the folder is not enabled then do not add watchers for the path.
567 if (!item.Enable)
568 {
569 continue;
570 }
571  
572 if (Directory.Exists(item.Path))
573 {
574 AddWatcher(item.Path, item.Recursive);
575 }
576 }
577 }
578  
10 office 579 _trackedFoldersChangedContinuation.Schedule(TimeSpan.FromSeconds(1), SaveFolders, _cancellationToken);
1 office 580 }
581  
582 private async void MainForm_Load(object sender, EventArgs e)
583 {
8 office 584 // attempt an upgrade
585 #pragma warning disable CS4014
586 PerformUpgrade();
587 #pragma warning restore CS4014
588  
9 office 589 // Set form properties.
590 toolStripMenuItem3.DropDown.Closing += toolStropMenuItem3DropDown_Closing;
591 eventsToolStripMenuItem.DropDown.Closing += eventsToolStripMenuItem_Closing;
592  
1 office 593 // Load configuration.
10 office 594 _configuration = await LoadConfiguration();
595 _configuration.PropertyChanged += _configuration_PropertyChanged;
596  
597 launchOnBootToolStripMenuItem.Checked = _configuration.LaunchOnBoot;
598 atomicOperationsToolStripMenuItem.Checked = _configuration.AtomicOperations;
14 office 599 autoNotesToolStripMenuItem.Checked = _configuration.AutoNotes;
10 office 600 gotifyToolStripMenuItem.Checked = _configuration.EnableGotify;
601 gotifyToolStripTextBox.Text = _configuration.GotifyURL;
602 enableToolStripMenuItem.Checked = _configuration.Enabled;
603 showBalloonTooltipsToolStripMenuItem.Checked = _configuration.Enabled;
604 windowToolStripMenuItem.Checked = _configuration.CaptureMode == CaptureMode.Window;
605 screenToolStripMenuItem.Checked = _configuration.CaptureMode == CaptureMode.Screen;
606 networkSharingToolStripMenuItem.Checked = _configuration.NetworkSharing;
15 office 607  
608 _searchEngine = new SearchEngine(_configuration, _cancellationToken);
609  
1 office 610 foreach (var item in attributesToolStripMenuItem.DropDownItems.OfType<ToolStripMenuItem>())
611 {
612 var text = item.Text;
613  
614 if (Enum.TryParse<NotifyFilters>(text, out var notifyFilter))
615 {
10 office 616 item.Checked = _configuration.NotifyFilters.HasFlag(notifyFilter);
1 office 617 }
618 }
619  
620 // Load all tracked folders.
4 office 621 try
622 {
623 var folders = await LoadFolders();
624 foreach (var folder in folders.Folder)
625 {
10 office 626 _trackedFolders.Folder.Add(folder);
4 office 627 }
1 office 628  
4 office 629 ToggleWatchers();
6 office 630  
631 return;
4 office 632 }
633 catch (FileNotFoundException)
1 office 634 {
4 office 635 ToggleWatchers();
636  
637 return;
1 office 638 }
5 office 639 catch(Exception exception)
640 {
641 Log.Error(exception, "Error loading tracked folders.");
642 }
1 office 643  
4 office 644 if (System.Windows.Forms.MessageBox.Show("Tracked folders could not be loaded, should they be deleted?", "Question", MessageBoxButtons.YesNo) == DialogResult.No)
645 {
646 return;
647 }
648  
1 office 649 ToggleWatchers();
650 }
651  
10 office 652 private void _configuration_PropertyChanged(object sender, PropertyChangedEventArgs e)
653 {
654 _changedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1), SaveConfiguration, _cancellationToken);
655 }
656  
1 office 657 private void ShowBalloonTooltipsToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
658 {
10 office 659 _configuration.ShowBalloonTooltips = ((ToolStripMenuItem)sender).Checked;
1 office 660 }
661  
662 private void LaunchOnBootToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
663 {
10 office 664 _configuration.LaunchOnBoot = ((ToolStripMenuItem)sender).Checked;
1 office 665  
10 office 666 Miscellaneous.LaunchOnBootSet(_configuration.LaunchOnBoot);
1 office 667 }
668  
669 private void AtomicOperationsToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
670 {
10 office 671 _configuration.AtomicOperations = ((ToolStripMenuItem)sender).Checked;
1 office 672 }
673  
674 private void TrashDatabaseToolStripMenuItem_Click(object sender, EventArgs e)
675 {
676 try
677 {
678 File.Delete(Constants.DatabaseFilePath);
679  
10 office 680 if (_configuration.ShowBalloonTooltips)
1 office 681 {
682 ShowBalloon("Database Deleted", $"Database file {Constants.DatabaseFilePath} has been deleted.",
683 5000);
684 }
685  
686 Log.Information($"Database file {Constants.DatabaseFilePath} has been deleted.");
687 }
688 catch (Exception exception)
689 {
10 office 690 if (_configuration.ShowBalloonTooltips)
1 office 691 {
692 ShowBalloon("Could not Delete Database",
693 $"Database file {Constants.DatabaseFilePath} delete failed with error: {exception.Message}",
694 5000);
695 }
696  
697 Log.Information(
698 $"Database file {Constants.DatabaseFilePath} delete failed with error: {exception.Message}");
699 }
700 }
701  
702 private void EnableToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
703 {
10 office 704 _configuration.Enabled = enableToolStripMenuItem.Checked;
1 office 705  
706 ToggleWatchers();
707 }
708  
709 private void SnapshotsToolStripMenuItem_Click(object sender, EventArgs e)
710 {
711 if (_snapshotManagerForm != null)
712 {
713 return;
714 }
715  
15 office 716 _snapshotManagerForm = new SnapshotManagerForm(_configuration, _trackedFolders, _fileSystemWatchers, _snapshotDatabase, _searchEngine, _cancellationToken);
1 office 717 _snapshotManagerForm.Closing += SnapshotManagerFormClosing;
718 _snapshotManagerForm.Show();
719 }
720  
721 private void SnapshotManagerFormClosing(object sender, CancelEventArgs e)
722 {
723 if (_snapshotManagerForm == null)
724 {
725 return;
726 }
727  
728 _snapshotManagerForm.Closing -= SnapshotManagerFormClosing;
729 _snapshotManagerForm.Close();
730 _snapshotManagerForm = null;
731 }
732  
733 private void FileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
734 {
735 Log.Information($"File deleted {e.Name}.");
736  
737 //ProcessFilesystemWatcherEvent(e.FullPath);
738 }
739  
740 private void FileSystemWatcher_Created(object sender, FileSystemEventArgs e)
741 {
742 Log.Information($"File created {e.Name}.");
743  
744 ProcessFilesystemWatcherEvent(e.FullPath);
745 }
746  
747 private void FileSystemWatcher_Renamed(object sender, RenamedEventArgs e)
748 {
749 Log.Information($"File renamed from {e.OldName} to {e.Name}.");
750  
751 ProcessFilesystemWatcherEvent(e.FullPath);
752 }
753  
754 private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
755 {
756 Log.Information($"File changed {e.Name}.");
757  
758 ProcessFilesystemWatcherEvent(e.FullPath);
759 }
760  
761 private void ProcessFilesystemWatcherEvent(string path)
762 {
763 // Ignore directories.
764 if (Directory.Exists(path))
765 {
766 return;
767 }
768 #pragma warning disable CS4014
769 Task.Run(async () =>
770 #pragma warning restore CS4014
771 {
772 await _changedFilesLock.WaitAsync(_cancellationToken);
773 try
774 {
775 var delay = global::TrackedFolders.Constants.Delay;
776 var color = Color.Empty;
777  
10 office 778 if (_trackedFolders.TryGet(path, out var folder))
1 office 779 {
780 delay = folder.Delay;
781 color = folder.Color;
782 }
783  
784 if (_changedFiles.Contains(path))
785 {
786 _changedFilesContinuation.Schedule(delay,
787 async () => await TakeSnapshots(color, _cancellationToken), _cancellationToken);
788 return;
789 }
790  
791 _changedFiles.Add(path);
792  
793 _changedFilesContinuation.Schedule(delay,
794 async () => await TakeSnapshots(color, _cancellationToken), _cancellationToken);
795 }
796 catch (Exception exception)
797 {
798 Log.Error(exception, "Could not process changed files.");
799 }
800 finally
801 {
802 _changedFilesLock.Release();
803 }
804 }, CancellationToken.None);
805 }
806  
807 private void AboutToolStripMenuItem_Click(object sender, EventArgs e)
808 {
809 if (_aboutForm != null)
810 {
811 return;
812 }
813  
814 _aboutForm = new AboutForm(_cancellationToken);
815 _aboutForm.Closing += AboutForm_Closing;
816 _aboutForm.Show();
817 }
818  
819 private void AboutForm_Closing(object sender, CancelEventArgs e)
820 {
821 if (_aboutForm == null)
822 {
823 return;
824 }
825  
826 _aboutForm.Dispose();
827 _aboutForm = null;
828 }
829  
830 private void QuitToolStripMenuItem_Click(object sender, EventArgs e)
831 {
832 Close();
833 }
834  
835 private async void UpdateToolStripMenuItem_Click(object sender, EventArgs e)
836 {
8 office 837 await PerformUpgrade();
1 office 838 }
839  
840 private void NotifyIcon1_Click(object sender, EventArgs e)
841 {
842 if (e is MouseEventArgs mouseEventArgs && mouseEventArgs.Button == MouseButtons.Left)
843 {
844 }
845 }
846  
847 private void ManageFoldersToolStripMenuItem_Click(object sender, EventArgs e)
848 {
849 if (_manageFoldersForm != null)
850 {
851 return;
852 }
853  
15 office 854 _manageFoldersForm = new ManageFoldersForm(_configuration, _trackedFolders, _snapshotDatabase, _searchEngine, _cancellationToken);
1 office 855 _manageFoldersForm.Closing += ManageFoldersForm_Closing;
856 _manageFoldersForm.Show();
857 }
858  
859 private void ManageFoldersForm_Closing(object sender, CancelEventArgs e)
860 {
861 if (_manageFoldersForm == null)
862 {
863 return;
864 }
865  
866 _manageFoldersForm.Closing -= ManageFoldersForm_Closing;
867 _manageFoldersForm.Close();
868 _manageFoldersForm = null;
869 }
870  
871 #endregion
872  
873 #region Public Methods
874  
875 public void ShowBalloon(string title, string text, int time)
876 {
877 notifyIcon1.BalloonTipTitle = title;
878 notifyIcon1.BalloonTipText = text;
879 notifyIcon1.ShowBalloonTip(time);
880 }
881  
882 public async Task SaveConfiguration()
883 {
884 if (!Directory.Exists(Constants.UserApplicationDirectory))
885 {
886 Directory.CreateDirectory(Constants.UserApplicationDirectory);
887 }
888  
10 office 889 switch (await Serialization.Serialize(_configuration, Constants.ConfigurationFile, "Configuration",
1 office 890 "<!ATTLIST Configuration xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
891 CancellationToken.None))
892 {
893 case SerializationSuccess<Configuration.Configuration> _:
894 Log.Information("Serialized configuration.");
895 break;
896 case SerializationFailure serializationFailure:
897 Log.Warning(serializationFailure.Exception.Message, "Failed to serialize configuration.");
898 break;
899 }
900 }
901  
902 public static async Task<Configuration.Configuration> LoadConfiguration()
903 {
904 if (!Directory.Exists(Constants.UserApplicationDirectory))
905 {
906 Directory.CreateDirectory(Constants.UserApplicationDirectory);
907 }
908  
909 var deserializationResult =
910 await Serialization.Deserialize<Configuration.Configuration>(Constants.ConfigurationFile,
911 Constants.ConfigurationNamespace, Constants.ConfigurationXsd, CancellationToken.None);
912  
913 switch (deserializationResult)
914 {
915 case SerializationSuccess<Configuration.Configuration> serializationSuccess:
916 return serializationSuccess.Result;
917 case SerializationFailure serializationFailure:
918 Log.Warning(serializationFailure.Exception, "Failed to load configuration.");
919 return new Configuration.Configuration();
920 default:
921 return new Configuration.Configuration();
922 }
923 }
924  
925 public static async Task<TrackedFolders.TrackedFolders> LoadFolders()
926 {
927 if (!Directory.Exists(Constants.UserApplicationDirectory))
928 {
929 Directory.CreateDirectory(Constants.UserApplicationDirectory);
930 }
931  
932 var deserializationResult =
933 await Serialization.Deserialize<TrackedFolders.TrackedFolders>(Constants.FoldersFile,
934 Constants.TrackedFoldersNamespace, Constants.TrackedFoldersXsd, CancellationToken.None);
935  
936 switch (deserializationResult)
937 {
938 case SerializationSuccess<TrackedFolders.TrackedFolders> serializationSuccess:
939 return serializationSuccess.Result;
940 case SerializationFailure serializationFailure:
941 Log.Warning(serializationFailure.Exception, "Failed to load tracked folders");
4 office 942 throw serializationFailure.Exception;
1 office 943 }
4 office 944  
945 return null;
1 office 946 }
947  
948 public async Task SaveFolders()
949 {
950 if (!Directory.Exists(Constants.UserApplicationDirectory))
951 {
952 Directory.CreateDirectory(Constants.UserApplicationDirectory);
953 }
954  
10 office 955 switch (await Serialization.Serialize(_trackedFolders, Constants.FoldersFile, "TrackedFolders",
1 office 956 "<!ATTLIST TrackedFolders xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
957 CancellationToken.None))
958 {
959 case SerializationSuccess<TrackedFolders.TrackedFolders> _:
960 Log.Information("Serialized tracked folders.");
961 break;
962 case SerializationFailure serializationFailure:
963 Log.Warning(serializationFailure.Exception.Message, "Failed to serialize tracked folders.");
964 break;
965 }
966 }
967  
968 #endregion
969  
970 #region Private Methods
971  
8 office 972 private async Task PerformUpgrade()
973 {
974 // Manually check for updates, this will not show a ui
975 var updateCheck = await _sparkle.CheckForUpdatesQuietly();
976 switch (updateCheck.Status)
977 {
978 case UpdateStatus.UserSkipped:
979 var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version;
980 updateCheck.Updates.Sort(UpdateComparer);
981 var latestVersion = updateCheck.Updates.FirstOrDefault();
982 if (latestVersion != null)
983 {
984 var availableVersion = new Version(latestVersion.Version);
985  
986 if (availableVersion <= assemblyVersion)
987 {
988 return;
989 }
990 }
991  
992 // Only offer an update nag screen if the version is one month old since skipping an update.
993 if (DateTime.Now.Subtract(latestVersion.PublicationDate).TotalDays < 30)
994 {
995 return;
996 }
997  
998 var decision = System.Windows.Forms.MessageBox.Show(
999 "Update available but it has been previously skipped and a month has passed since. Should the update proceed now?",
1000 Assembly.GetExecutingAssembly().GetName().Name, MessageBoxButtons.YesNo,
1001 MessageBoxIcon.Asterisk,
1002 MessageBoxDefaultButton.Button1, System.Windows.Forms.MessageBoxOptions.DefaultDesktopOnly, false);
1003  
1004 if (decision.HasFlag(DialogResult.No))
1005 {
1006 return;
1007 }
1008  
1009 goto default;
1010 case UpdateStatus.UpdateNotAvailable:
1011 System.Windows.Forms.MessageBox.Show("No updates available at this time.",
1012 Assembly.GetExecutingAssembly().GetName().Name, MessageBoxButtons.OK,
1013 MessageBoxIcon.Asterisk,
1014 MessageBoxDefaultButton.Button1, System.Windows.Forms.MessageBoxOptions.DefaultDesktopOnly, false);
1015 break;
1016 case UpdateStatus.CouldNotDetermine:
1017 Log.Error("Could not determine the update availability status.");
1018 break;
1019 default:
1020 _sparkle.ShowUpdateNeededUI();
1021 break;
1022 }
1023 }
1024  
1025 private static int UpdateComparer(AppCastItem x, AppCastItem y)
1026 {
1027 if (x == null)
1028 {
1029 return 1;
1030 }
1031  
1032 if (y == null)
1033 {
1034 return -1;
1035 }
1036  
1037 if (x == y)
1038 {
1039 return 0;
1040 }
1041  
1042 return new Version(y.Version).CompareTo(new Version(x.Version));
1043 }
1044  
1 office 1045 private void RemoveWatcher(string folder)
1046 {
10 office 1047 var count = _fileSystemWatchers.Count;
1048 while (_fileSystemWatchers.TryDequeue(out var fileSystemWatcher))
1 office 1049 {
10 office 1050 if(--count == 0)
1051 {
1052 break;
1053 }
1054  
1055 if (fileSystemWatcher.Path.IsPathEqual(folder) ||
1 office 1056 fileSystemWatcher.Path.IsSubPathOf(folder))
1057 {
10 office 1058 continue;
1 office 1059 }
10 office 1060  
1061 _fileSystemWatchers.Enqueue(fileSystemWatcher);
1 office 1062 }
1063  
1064 }
1065  
1066 private void AddWatcher(string folder, bool recursive)
1067 {
1068 var fileSystemWatcher = new FileSystemWatcher
1069 {
1070 IncludeSubdirectories = recursive,
1071 NotifyFilter = _fileSystemWatchersNotifyFilters,
1072 Path = folder,
1073 EnableRaisingEvents = true,
1074 InternalBufferSize = 65536
1075 };
1076  
1077 fileSystemWatcher.Changed += FileSystemWatcher_Changed;
1078 fileSystemWatcher.Renamed += FileSystemWatcher_Renamed;
1079 fileSystemWatcher.Created += FileSystemWatcher_Created;
1080 fileSystemWatcher.Deleted += FileSystemWatcher_Deleted;
1081  
10 office 1082 _fileSystemWatchers.Enqueue(fileSystemWatcher);
1 office 1083 }
1084  
1085 private void ToggleWatchers()
1086 {
10 office 1087 switch (_configuration.Enabled)
1 office 1088 {
1089 case true:
10 office 1090 foreach (var watcher in _fileSystemWatchers)
1 office 1091 {
1092 watcher.EnableRaisingEvents = true;
1093 }
1094  
10 office 1095 if (_configuration.ShowBalloonTooltips)
1 office 1096 {
1097 ShowBalloon("Watching", "Watching folders...", 5000);
1098 }
1099  
1100 Log.Information("Watching folders.");
1101  
1102 break;
1103 default:
10 office 1104 foreach (var watcher in _fileSystemWatchers)
1 office 1105 {
1106 watcher.EnableRaisingEvents = false;
1107 }
1108  
10 office 1109 if (_configuration.ShowBalloonTooltips)
1 office 1110 {
1111 ShowBalloon("Not Watching", "Folders are not being watched.", 5000);
1112 }
1113  
1114 Log.Information("Folders are not being watched.");
1115  
1116 break;
1117 }
1118 }
1119  
1120 private async Task TakeSnapshots(Color color, CancellationToken cancellationToken)
1121 {
13 office 1122  
1 office 1123 var bufferBlock = new BufferBlock<string>(new DataflowBlockOptions() {CancellationToken = cancellationToken});
1124 var actionBlock = new ActionBlock<string>(async path =>
1125 {
1126 // In case files have vanished strictly due to the time specified by the tracked folders delay.
1127 if (!File.Exists(path))
1128 {
1129 Log.Warning($"File vanished after tracked folder delay: {path}");
1130  
1131 return;
1132 }
1133  
1134 try
1135 {
13 office 1136 var fileName = Path.GetFileName(path);
10 office 1137 var screenCapture = ScreenCapture.Capture((Utilities.CaptureMode)_configuration.CaptureMode);
1 office 1138  
15 office 1139 var terms = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
1140  
1141 if (_configuration.AutoNotes)
13 office 1142 {
1143 await foreach (var term in Extensions.RecognizeStrings(screenCapture, cancellationToken))
1144 {
1145 terms.Add(term);
1146 }
15 office 1147 }
13 office 1148  
15 office 1149 var note = string.Join(" ", terms);
1150  
1151 // async, decompose, branch
1152 if (await _snapshotDatabase.CreateSnapshotAsync(fileName, path, screenCapture, color, note, cancellationToken) is Snapshot snapshot)
1153 {
1154 await _searchEngine.Index(snapshot, cancellationToken);
13 office 1155 }
1 office 1156 }
1157 catch (SQLiteException exception)
1158 {
1159 if (exception.ResultCode == SQLiteErrorCode.Constraint)
1160 {
1161 Log.Information(exception, "Snapshot already exists.");
1162 }
1163 }
1164 catch (Exception exception)
1165 {
1166 Log.Error(exception, $"Could not take snapshot of file {path}");
1167 }
1168 });
1169  
13 office 1170 using var snapshotLink = bufferBlock.LinkTo(actionBlock, new DataflowLinkOptions() { PropagateCompletion = true });
1171  
1172 await _changedFilesLock.WaitAsync(_cancellationToken);
1173 try
1 office 1174 {
13 office 1175 foreach (var path in _changedFiles)
1 office 1176 {
13 office 1177 await bufferBlock.SendAsync(path, cancellationToken);
1 office 1178 }
13 office 1179 bufferBlock.Complete();
1180 await bufferBlock.Completion;
1 office 1181 }
13 office 1182 catch (Exception exception)
1183 {
1184 Log.Error(exception, "Could not take snapshots.");
1185 }
1186 finally
1187 {
1188 _changedFiles.Clear();
1189 _changedFilesLock.Release();
1190 }
1 office 1191 }
1192  
7 office 1193 private async Task SendGotifyNotification(string v1, string v2)
1194 {
10 office 1195 if (!Uri.TryCreate(_configuration.GotifyURL, UriKind.RelativeOrAbsolute, out var uri))
7 office 1196 {
1197 Log.Warning($"Invalid Gotify URL provided.");
1198 return;
1199 }
1200 var gotifyMessageSending = new GotifyMessageOutgoing()
1201 {
1202 Title = v1,
1203 Message = v2
1204 };
1205  
1206 var payload = JsonConvert.SerializeObject(gotifyMessageSending);
1207 using var stringContent = new StringContent(payload, Encoding.UTF8, "application/json");
1208 using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, uri);
1209 httpRequestMessage.Content = stringContent;
1210 var response = await _httpClient.SendAsync(httpRequestMessage, _cancellationToken);
1211 var responseContent = await response.Content.ReadAsStringAsync();
1212 var gotifyReply = JsonConvert.DeserializeObject<GotifyMessageIncoming>(responseContent);
1213 if (gotifyReply?.AppId == null)
1214 {
1215 Log.Error($"Failed Sending notification.");
1216 }
1217 }
1218  
1 office 1219 #endregion
13 office 1220  
1221  
1 office 1222 }
1223 }