Horizon – Blame information for rev 17
?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 | |||
17 | office | 89 | private SparkleUpdater _sparkle; |
1 | office | 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 | |||
151 | /// <summary> |
||
152 | /// Clean up any resources being used. |
||
153 | /// </summary> |
||
154 | /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> |
||
155 | protected override void Dispose(bool disposing) |
||
156 | { |
||
157 | if (disposing && components != null) |
||
158 | { |
||
159 | toolStripMenuItem3.DropDown.Closing -= toolStropMenuItem3DropDown_Closing; |
||
7 | office | 160 | eventsToolStripMenuItem.DropDown.Closing -= eventsToolStripMenuItem_Closing; |
161 | |||
1 | office | 162 | components.Dispose(); |
163 | } |
||
164 | |||
165 | _cancellationTokenSource.Cancel(); |
||
10 | office | 166 | _configuration.PropertyChanged -= _configuration_PropertyChanged; |
1 | office | 167 | |
7 | office | 168 | _snapshotDatabase.SnapshotNoteUpdate -= _snapshotDatabase_SnapshotNoteUpdate; |
169 | _snapshotDatabase.SnapshotDataUpdate -= _snapshotDatabase_SnapshotDataUpdate; |
||
1 | office | 170 | _snapshotDatabase.SnapshotRevert -= SnapshotDatabase_SnapshotRevert; |
7 | office | 171 | _snapshotDatabase.SnapshotCreate -= SnapshotDatabase_SnapshotCreateAsync; |
1 | office | 172 | _snapshotDatabase.SnapshotTransferReceived -= SnapshotDatabase_SnapshotTransferReceived; |
173 | |||
174 | _snapshotDatabase.Dispose(); |
||
175 | |||
176 | base.Dispose(disposing); |
||
177 | } |
||
178 | |||
7 | office | 179 | private void eventsToolStripMenuItem_Closing(object sender, ToolStripDropDownClosingEventArgs e) |
180 | { |
||
181 | if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked) |
||
182 | { |
||
183 | e.Cancel = true; |
||
184 | } |
||
185 | } |
||
186 | |||
1 | office | 187 | private void toolStropMenuItem3DropDown_Closing(object sender, ToolStripDropDownClosingEventArgs e) |
188 | { |
||
189 | if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked) |
||
190 | { |
||
191 | e.Cancel = true; |
||
192 | } |
||
193 | } |
||
194 | |||
195 | #endregion |
||
196 | |||
197 | #region Event Handlers |
||
7 | office | 198 | |
13 | office | 199 | private void autoNotesToolStripMenuItem_Click(object sender, EventArgs e) |
200 | { |
||
201 | _configuration.AutoNotes = ((ToolStripMenuItem)sender).Checked; |
||
202 | } |
||
203 | |||
7 | office | 204 | private void gotfyToolStripTextBox_TextChanged(object sender, EventArgs e) |
205 | { |
||
206 | var toolStripTextBox = ((ToolStripTextBox)sender); |
||
207 | |||
10 | office | 208 | _configuration.GotifyURL = toolStripTextBox.Text; |
7 | office | 209 | } |
210 | |||
211 | private void eventsToolStripMenuItem_CheckStateChanged(object sender, EventArgs e) |
||
212 | { |
||
213 | var toolStripMenuItem = (ToolStripMenuItem)sender; |
||
214 | |||
215 | var text = toolStripMenuItem.Text; |
||
216 | var state = toolStripMenuItem.CheckState; |
||
217 | |||
218 | foreach (var flag in Enum.GetNames(typeof(NotifyEvent))) |
||
219 | { |
||
220 | if (string.Equals(flag, text, StringComparison.OrdinalIgnoreCase)) |
||
221 | { |
||
222 | if (Enum.TryParse<NotifyEvent>(flag, true, out var setting)) |
||
223 | { |
||
224 | switch (state) |
||
225 | { |
||
226 | case CheckState.Checked: |
||
10 | office | 227 | _configuration.NotifyEvents = _configuration.NotifyEvents | setting; |
7 | office | 228 | break; |
229 | case CheckState.Unchecked: |
||
10 | office | 230 | _configuration.NotifyEvents = _configuration.NotifyEvents & ~setting; |
7 | office | 231 | break; |
232 | } |
||
233 | |||
234 | } |
||
235 | } |
||
236 | } |
||
237 | } |
||
238 | |||
239 | private void gotifyToolStripMenuItem_CheckedChanged(object sender, EventArgs e) |
||
240 | { |
||
10 | office | 241 | _configuration.EnableGotify = ((ToolStripMenuItem)sender).Checked; |
7 | office | 242 | } |
243 | |||
1 | office | 244 | private void attributesToolStripMenuItem_CheckStateChanged(object sender, EventArgs e) |
245 | { |
||
246 | var toolStripMenuItem = (ToolStripMenuItem)sender; |
||
247 | |||
248 | var text = toolStripMenuItem.Text; |
||
249 | var state = toolStripMenuItem.CheckState; |
||
250 | |||
251 | foreach (var flag in Enum.GetNames(typeof(NotifyFilters))) |
||
252 | { |
||
253 | if (string.Equals(flag, text, StringComparison.OrdinalIgnoreCase)) |
||
254 | { |
||
255 | if (Enum.TryParse<NotifyFilters>(flag, true, out var setting)) |
||
256 | { |
||
257 | switch (state) |
||
258 | { |
||
259 | case CheckState.Checked: |
||
260 | _fileSystemWatchersNotifyFilters = _fileSystemWatchersNotifyFilters | setting; |
||
261 | break; |
||
262 | case CheckState.Unchecked: |
||
263 | _fileSystemWatchersNotifyFilters = _fileSystemWatchersNotifyFilters & ~setting; |
||
264 | break; |
||
265 | } |
||
266 | |||
267 | } |
||
268 | } |
||
269 | } |
||
270 | |||
10 | office | 271 | _configuration.NotifyFilters = _fileSystemWatchersNotifyFilters; |
1 | office | 272 | } |
273 | |||
274 | private void networkSharingToolStripMenuItem_CheckStateChanged(object sender, EventArgs e) |
||
275 | { |
||
276 | var toolStripMenuItem = (ToolStripMenuItem)sender; |
||
277 | |||
278 | switch (toolStripMenuItem.CheckState) |
||
279 | { |
||
280 | case CheckState.Checked: |
||
281 | var freePort = GetAvailableTcpPort(); |
||
282 | |||
283 | _horizonNetworkShare = new WatsonTcpServer("0.0.0.0", freePort); |
||
284 | _horizonNetworkShare.Events.ClientConnected += Events_ClientConnected; |
||
285 | _horizonNetworkShare.Events.ClientDisconnected += Events_ClientDisconnected; |
||
286 | _horizonNetworkShare.Events.MessageReceived += Events_MessageReceived; |
||
287 | _horizonNetworkShare.Events.ExceptionEncountered += Events_ExceptionEncountered; |
||
288 | #pragma warning disable CS4014 |
||
289 | _horizonNetworkShare.Start(); |
||
290 | #pragma warning restore CS4014 |
||
291 | |||
292 | try |
||
293 | { |
||
294 | _horizonDiscoveryService = new RegisterService(); |
||
295 | _horizonDiscoveryService.Name = $"Horizon ({Environment.MachineName})"; |
||
296 | _horizonDiscoveryService.RegType = "_horizon._tcp"; |
||
297 | _horizonDiscoveryService.ReplyDomain = "local."; |
||
298 | _horizonDiscoveryService.UPort = freePort; |
||
299 | _horizonDiscoveryService.Register(); |
||
300 | } |
||
301 | catch (Exception exception) |
||
302 | { |
||
303 | Log.Error(exception, "Service discovery protocol could not be stared."); |
||
304 | } |
||
305 | |||
10 | office | 306 | _configuration.NetworkSharing = true; |
1 | office | 307 | break; |
308 | case CheckState.Unchecked: |
||
309 | if (_horizonNetworkShare != null) |
||
310 | { |
||
311 | _horizonNetworkShare.Events.ClientConnected -= Events_ClientConnected; |
||
312 | _horizonNetworkShare.Events.ClientDisconnected -= Events_ClientDisconnected; |
||
313 | _horizonNetworkShare.Events.MessageReceived -= Events_MessageReceived; |
||
314 | _horizonNetworkShare.Events.ExceptionEncountered -= Events_ExceptionEncountered; |
||
315 | |||
316 | _horizonNetworkShare.Dispose(); |
||
317 | _horizonNetworkShare = null; |
||
318 | } |
||
319 | |||
320 | if (_horizonDiscoveryService != null) |
||
321 | { |
||
322 | _horizonDiscoveryService.Dispose(); |
||
323 | _horizonDiscoveryService = null; |
||
324 | |||
325 | } |
||
326 | |||
10 | office | 327 | _configuration.NetworkSharing = false; |
1 | office | 328 | break; |
329 | } |
||
330 | } |
||
331 | |||
332 | private void Events_ExceptionEncountered(object sender, ExceptionEventArgs e) |
||
333 | { |
||
334 | Log.Error(e.Exception,$"Client threw exception."); |
||
335 | } |
||
336 | |||
337 | private async void Events_MessageReceived(object sender, MessageReceivedEventArgs e) |
||
338 | { |
||
339 | Log.Information($"Client {e.Client?.IpPort} sent {e.Data?.Length} bytes via network sharing."); |
||
340 | |||
341 | if (e.Data?.Length == 0) |
||
342 | { |
||
343 | return; |
||
344 | } |
||
345 | |||
346 | try |
||
347 | { |
||
12 | office | 348 | //var payload = Encoding.UTF8.GetString(); |
1 | office | 349 | |
12 | office | 350 | using var memoryStream = new MemoryStream(e.Data); |
351 | using var streamReader = new StreamReader(memoryStream); |
||
352 | using var jsonTextReader = new JsonTextReader(streamReader); |
||
15 | office | 353 | var completeSnapshot = _jsonSerializer.Deserialize<Snapshot>(jsonTextReader); |
1 | office | 354 | |
15 | office | 355 | await _snapshotDatabase.ApplySnapshotAsync(completeSnapshot, _cancellationToken); |
1 | office | 356 | |
357 | Log.Information($"Stored {completeSnapshot.Name} from {e.Client?.IpPort}"); |
||
358 | } |
||
359 | catch (Exception exception) |
||
360 | { |
||
361 | Log.Error(exception, $"Failed to process network share from {e.Client?.IpPort}."); |
||
362 | } |
||
363 | } |
||
364 | |||
365 | private void Events_ClientDisconnected(object sender, WatsonTcp.DisconnectionEventArgs e) |
||
366 | { |
||
367 | Log.Information($"Client {e.Client?.IpPort} disconnected from network sharing."); |
||
368 | } |
||
369 | |||
370 | private void Events_ClientConnected(object sender, WatsonTcp.ConnectionEventArgs e) |
||
371 | { |
||
372 | Log.Information($"Client {e.Client?.IpPort} connected to network sharing."); |
||
373 | } |
||
374 | |||
375 | private void WindowToolStripMenuItem_Click(object sender, EventArgs e) |
||
376 | { |
||
377 | windowToolStripMenuItem.Checked = true; |
||
378 | screenToolStripMenuItem.Checked = false; |
||
379 | |||
10 | office | 380 | _configuration.CaptureMode = CaptureMode.Window; |
1 | office | 381 | } |
382 | |||
383 | private void ScreenToolStripMenuItem_Click(object sender, EventArgs e) |
||
384 | { |
||
385 | screenToolStripMenuItem.Checked = true; |
||
386 | windowToolStripMenuItem.Checked = false; |
||
387 | |||
10 | office | 388 | _configuration.CaptureMode = CaptureMode.Screen; |
1 | office | 389 | } |
390 | |||
391 | private void LogViewToolStripMenuItem_Click(object sender, EventArgs e) |
||
392 | { |
||
393 | if (_logViewForm != null) |
||
394 | { |
||
395 | return; |
||
396 | } |
||
397 | |||
398 | _logViewForm = new LogViewForm(this, _memorySink, _cancellationToken); |
||
399 | _logViewForm.Closing += LogViewFormClosing; |
||
400 | _logViewForm.Show(); |
||
401 | } |
||
402 | |||
403 | private void LogViewFormClosing(object sender, CancelEventArgs e) |
||
404 | { |
||
405 | if (_logViewForm == null) |
||
406 | { |
||
407 | return; |
||
408 | } |
||
409 | |||
410 | _logViewForm.Closing -= LogViewFormClosing; |
||
411 | _logViewForm.Close(); |
||
412 | _logViewForm = null; |
||
413 | } |
||
414 | |||
7 | office | 415 | private async void SnapshotDatabase_SnapshotCreateAsync(object sender, SnapshotCreateEventArgs e) |
1 | office | 416 | { |
417 | switch (e) |
||
418 | { |
||
419 | case SnapshotCreateSuccessEventArgs snapshotCreateSuccessEventArgs: |
||
13 | office | 420 | var snapshot = snapshotCreateSuccessEventArgs.Snapshot; |
10 | office | 421 | if (_configuration.ShowBalloonTooltips) |
1 | office | 422 | { |
13 | office | 423 | ShowBalloon("Snapshot Created", $"Took a snapshot of {snapshot.Path}.", |
1 | office | 424 | 5000); |
425 | } |
||
426 | |||
10 | office | 427 | if(_configuration.EnableGotify && _configuration.NotifyEvents.HasFlag(NotifyEvent.Create)) |
7 | office | 428 | { |
13 | office | 429 | await SendGotifyNotification("Snapshot Created", $"Took a snapshot of {snapshot.Name}."); |
7 | office | 430 | } |
431 | |||
13 | office | 432 | Log.Information($"Took a snapshot of {snapshot.Path}."); |
1 | office | 433 | |
434 | break; |
||
435 | case SnapshotCreateFailureEventArgs snapshotCreateFailureEventArgs: |
||
10 | office | 436 | if (_configuration.ShowBalloonTooltips) |
1 | office | 437 | { |
438 | ShowBalloon("Snapshot Failed", |
||
439 | $"Failed to take a snapshot of {snapshotCreateFailureEventArgs.Path}.", 5000); |
||
440 | } |
||
441 | |||
442 | Log.Information($"Failed to take a snapshot of {snapshotCreateFailureEventArgs.Path}."); |
||
443 | |||
444 | break; |
||
445 | } |
||
446 | } |
||
7 | office | 447 | private async void _snapshotDatabase_SnapshotDataUpdate(object sender, SnapshotDataUpdateEventArgs e) |
448 | { |
||
449 | switch(e) |
||
450 | { |
||
451 | case SnapshotDataUpdateSuccessEventArgs snapshotDataUpdateSuccessEventArgs: |
||
10 | office | 452 | if (_configuration.EnableGotify && _configuration.NotifyEvents.HasFlag(NotifyEvent.Update)) |
7 | office | 453 | { |
454 | await SendGotifyNotification("Snapshot Updated", $"Snapshot data updated {snapshotDataUpdateSuccessEventArgs.NewHash} from {snapshotDataUpdateSuccessEventArgs.OldHash}."); |
||
455 | } |
||
456 | break; |
||
457 | } |
||
458 | } |
||
1 | office | 459 | |
7 | office | 460 | private async void _snapshotDatabase_SnapshotNoteUpdate(object sender, SnapshotNoteUpdateEventArgs e) |
1 | office | 461 | { |
7 | office | 462 | switch(e) |
463 | { |
||
464 | case SnapshotNoteUpdateSuccessEventArgs snapshotNoteUpdateSuccessEventArgs: |
||
10 | office | 465 | if (_configuration.EnableGotify && _configuration.NotifyEvents.HasFlag(NotifyEvent.Update)) |
7 | office | 466 | { |
467 | await SendGotifyNotification("Snapshot Updated", $"Snapshot note updated for {snapshotNoteUpdateSuccessEventArgs.Hash}."); |
||
468 | } |
||
469 | break; |
||
470 | } |
||
471 | |||
472 | } |
||
473 | |||
474 | private async void SnapshotDatabase_SnapshotTransferReceived(object sender, SnapshotCreateEventArgs e) |
||
475 | { |
||
1 | office | 476 | switch (e) |
477 | { |
||
478 | case SnapshotCreateSuccessEventArgs snapshotCreateSuccessEventArgs: |
||
13 | office | 479 | var snapshot = snapshotCreateSuccessEventArgs.Snapshot; |
10 | office | 480 | if (_configuration.ShowBalloonTooltips) |
1 | office | 481 | { |
13 | office | 482 | ShowBalloon("Snapshot Transfer Success", $"A snapshot has been transferred {snapshot.Path}.", |
1 | office | 483 | 5000); |
484 | } |
||
485 | |||
10 | office | 486 | if (_configuration.EnableGotify && _configuration.NotifyEvents.HasFlag(NotifyEvent.Transfer)) |
7 | office | 487 | { |
13 | office | 488 | await SendGotifyNotification("Snapshot Transerred", $"A snapshot has been transferred {snapshot.Name}."); |
7 | office | 489 | } |
490 | |||
13 | office | 491 | Log.Information($"A snapshot transfer succeeded {snapshot.Path}."); |
1 | office | 492 | |
493 | break; |
||
494 | case SnapshotCreateFailureEventArgs snapshotCreateFailureEventArgs: |
||
10 | office | 495 | if (_configuration.ShowBalloonTooltips) |
1 | office | 496 | { |
497 | ShowBalloon("Snapshot Transfer Failure", |
||
498 | $"A snapshot failed to transfer {snapshotCreateFailureEventArgs.Path}.", 5000); |
||
499 | } |
||
500 | |||
501 | Log.Information($"A snapshot failed to transfer {snapshotCreateFailureEventArgs.Path}."); |
||
502 | |||
503 | break; |
||
504 | } |
||
505 | } |
||
506 | |||
7 | office | 507 | private async void SnapshotDatabase_SnapshotRevert(object sender, SnapshotRevertEventArgs e) |
1 | office | 508 | { |
509 | switch (e) |
||
510 | { |
||
511 | case SnapshotRevertSuccessEventArgs snapshotRevertSuccessEventArgs: |
||
10 | office | 512 | if (_configuration.ShowBalloonTooltips) |
1 | office | 513 | { |
514 | ShowBalloon("Revert Succeeded", $"File {snapshotRevertSuccessEventArgs.Name} reverted.", 5000); |
||
515 | } |
||
516 | |||
10 | office | 517 | if (_configuration.EnableGotify && _configuration.NotifyEvents.HasFlag(NotifyEvent.Revert)) |
7 | office | 518 | { |
519 | await SendGotifyNotification("Snapshot Reverted", $"Reverted a snapshot of {snapshotRevertSuccessEventArgs.Name}."); |
||
520 | } |
||
521 | |||
522 | |||
1 | office | 523 | Log.Information($"File {snapshotRevertSuccessEventArgs.Name} reverted."); |
524 | |||
525 | break; |
||
526 | case SnapshotRevertFailureEventArgs snapshotRevertFailureEventArgs: |
||
10 | office | 527 | if (_configuration.ShowBalloonTooltips) |
1 | office | 528 | { |
529 | ShowBalloon("Revert Failed", $"Reverting file {snapshotRevertFailureEventArgs.Name} failed.", |
||
530 | 5000); |
||
531 | } |
||
532 | |||
533 | Log.Information($"Reverting file {snapshotRevertFailureEventArgs.Name} failed."); |
||
534 | |||
535 | break; |
||
536 | } |
||
537 | } |
||
538 | |||
10 | office | 539 | private void Folder_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) |
1 | office | 540 | { |
541 | if (e.OldItems != null) |
||
542 | { |
||
543 | foreach (var item in e.OldItems.OfType<Folder>()) |
||
544 | { |
||
545 | RemoveWatcher(item.Path); |
||
546 | } |
||
547 | } |
||
548 | |||
549 | if (e.NewItems != null) |
||
550 | { |
||
551 | foreach (var item in e.NewItems.OfType<Folder>()) |
||
552 | { |
||
553 | // If the folder is not enabled then do not add watchers for the path. |
||
554 | if (!item.Enable) |
||
555 | { |
||
556 | continue; |
||
557 | } |
||
558 | |||
559 | if (Directory.Exists(item.Path)) |
||
560 | { |
||
561 | AddWatcher(item.Path, item.Recursive); |
||
562 | } |
||
563 | } |
||
564 | } |
||
565 | |||
10 | office | 566 | _trackedFoldersChangedContinuation.Schedule(TimeSpan.FromSeconds(1), SaveFolders, _cancellationToken); |
1 | office | 567 | } |
568 | |||
569 | private async void MainForm_Load(object sender, EventArgs e) |
||
570 | { |
||
17 | office | 571 | // Start application update. |
572 | var manifestModuleName = Assembly.GetEntryAssembly().ManifestModule.FullyQualifiedName; |
||
573 | var icon = Icon.ExtractAssociatedIcon(manifestModuleName); |
||
574 | |||
575 | _sparkle = new SparkleUpdater("https://horizon.grimore.org/update/appcast.xml", |
||
576 | new Ed25519Checker(SecurityMode.Strict, "LonrgxVjSF0GnY4hzwlRJnLkaxnDn2ikdmOifILzLJY=")) |
||
577 | { |
||
578 | UIFactory = new UIFactory(icon), |
||
579 | RelaunchAfterUpdate = true |
||
580 | }; |
||
581 | |||
582 | await _sparkle.StartLoop(true, true); |
||
583 | |||
8 | office | 584 | // attempt an upgrade |
585 | #pragma warning disable CS4014 |
||
17 | office | 586 | //PerformUpgrade(); |
8 | office | 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 | } |