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