Horizon – Blame information for rev 15
?pathlinks?
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 | } |