Horizon – Blame information for rev 13

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 using System;
2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.ComponentModel;
5 using System.Data.SQLite;
6 using System.Diagnostics;
7 using System.Drawing;
8 using System.IO;
12 office 9 using System.IO.Ports;
1 office 10 using System.Linq;
12 office 11 using System.Net.Sockets;
1 office 12 using System.Security.Cryptography;
12 office 13 using System.Text;
1 office 14 using System.Threading;
15 using System.Threading.Tasks;
16 using System.Windows.Forms;
17 using Horizon.Database;
18 using Horizon.Utilities;
19 using Microsoft.WindowsAPICodePack.Dialogs;
12 office 20 using Mono.Zeroconf;
21 using Mono.Zeroconf.Providers.Bonjour;
22 using Newtonsoft.Json;
5 office 23 using Org.BouncyCastle.Crypto;
12 office 24 using Org.BouncyCastle.Utilities.Net;
1 office 25 using Serilog;
12 office 26 using WatsonTcp;
1 office 27  
28 namespace Horizon.Snapshots
29 {
30 public partial class SnapshotManagerForm : Form
31 {
32 #region Static Fields and Constants
33  
34 private static ScheduledContinuation _searchTextBoxChangedContinuation;
35  
36 #endregion
37  
38 #region Public Events & Delegates
39  
40 public event EventHandler<PreviewRetrievedEventArgs> PreviewRetrieved;
41  
42 #endregion
43  
44 #region Private Delegates, Events, Enums, Properties, Indexers and Fields
45  
46 private readonly MainForm _mainForm;
47  
48 private readonly SnapshotDatabase _snapshotDatabase;
49  
50 private HexViewForm _hexViewForm;
51  
52 private SnapshotNoteForm _snapshotNote;
53  
54 private SnapshotPreviewForm _snapshotPreviewForm;
55  
56 private readonly object _mouseMoveLock = new object();
57  
58 private readonly CancellationTokenSource _cancellationTokenSource;
59  
60 private readonly CancellationToken _cancellationToken;
61  
62 private readonly CancellationTokenSource _localCancellationTokenSource;
63  
64 private readonly CancellationToken _localCancellationToken;
65  
12 office 66 private readonly Mono.Zeroconf.ServiceBrowser _horizonServiceBrowser;
67  
68 private IResolvableService _resolvedService;
69  
70 private readonly ConcurrentDictionary<string, Service> _discoveredHorizonNetworkShares;
71  
1 office 72 #endregion
73  
74 #region Constructors, Destructors and Finalizers
75  
5 office 76 private SnapshotManagerForm()
1 office 77 {
78 InitializeComponent();
79  
80 dataGridView1.Columns["TimeColumn"].ValueType = typeof(DateTime);
81  
82 _searchTextBoxChangedContinuation = new ScheduledContinuation();
83  
84 _localCancellationTokenSource = new CancellationTokenSource();
85 _localCancellationToken = _localCancellationTokenSource.Token;
12 office 86  
87 _discoveredHorizonNetworkShares = new ConcurrentDictionary<string, Service>();
88 _horizonServiceBrowser = new Mono.Zeroconf.ServiceBrowser();
89 _horizonServiceBrowser.ServiceAdded += OnHorizonServiceBrowserOnServiceAdded;
1 office 90 }
91  
92 public SnapshotManagerForm(MainForm mainForm, SnapshotDatabase snapshotDatabase,
93 CancellationToken cancellationToken) : this()
94 {
95 _mainForm = mainForm;
96 _snapshotDatabase = snapshotDatabase;
97 _snapshotDatabase.SnapshotCreate += SnapshotManager_SnapshotCreate;
98  
99 _cancellationTokenSource =
100 CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, cancellationToken);
101 _cancellationToken = _cancellationTokenSource.Token;
102 }
103  
104 /// <summary>
105 /// Clean up any resources being used.
106 /// </summary>
107 /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
108 protected override void Dispose(bool disposing)
109 {
110 if (disposing && components != null)
111 {
112 components.Dispose();
113 }
114  
115 _snapshotDatabase.SnapshotCreate -= SnapshotManager_SnapshotCreate;
116  
12 office 117 _horizonServiceBrowser.ServiceAdded -= OnHorizonServiceBrowserOnServiceAdded;
118 _horizonServiceBrowser.Dispose();
1 office 119 _localCancellationTokenSource.Cancel();
120  
121 base.Dispose(disposing);
122 }
123  
124 #endregion
125  
126 #region Event Handlers
12 office 127 private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
128 {
129 }
130  
131 private void contextMenuStrip1_Opened(object sender, EventArgs e)
132 {
133 }
134  
1 office 135 private void DataGridView1_MouseDown(object sender, MouseEventArgs e)
136 {
137 var dataGridView = (DataGridView)sender;
138  
139 var index = dataGridView.HitTest(e.X, e.Y).RowIndex;
140  
141 if (index == -1)
142 {
143 base.OnMouseDown(e);
144 return;
145 }
146  
147 if (!dataGridView.SelectedRows.Contains(dataGridView.Rows[index]))
148 {
149 base.OnMouseDown(e);
150 }
151 }
152  
153 private async void DataGridView1_MouseMove(object sender, MouseEventArgs e)
154 {
155 var dataGridView = (DataGridView)sender;
156  
157 // Only accept dragging with left mouse button.
158 switch (e.Button)
159 {
160 case MouseButtons.Left:
161  
162 if (!Monitor.TryEnter(_mouseMoveLock))
163 {
164 break;
165 }
166  
167 try
168 {
169 var index = dataGridView.HitTest(e.X, e.Y).RowIndex;
170  
171 if (index == -1)
172 {
173 base.OnMouseMove(e);
174 return;
175 }
176  
177 var rows = GetSelectedDataGridViewRows(dataGridView);
178  
179 var count = rows.Count;
180  
181 if (count == 0)
182 {
183 base.OnMouseMove(e);
184 break;
185 }
186  
187 toolStripProgressBar1.Minimum = 0;
188 toolStripProgressBar1.Maximum = count;
189  
190 var virtualFileDataObject = new VirtualFileDataObject.VirtualFileDataObject();
191 var fileDescriptors =
192 new List<VirtualFileDataObject.VirtualFileDataObject.FileDescriptor>(count);
193  
194 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
195 {
196 if (_cancellationToken.IsCancellationRequested)
197 {
198 return;
199 }
200  
201 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
202 {
203 Log.Error(rowProgressFailure.Exception, "Unable to retrieve data for row.");
204  
205 toolStripStatusLabel1.Text =
206 $"Could not read file data {rowProgress.Row.Cells["NameColumn"].Value}...";
207 toolStripProgressBar1.Value = rowProgress.Index + 1;
208  
209 statusStrip1.Update();
210  
211 return;
212 }
213  
214 if (rowProgress is DataGridViewRowProgressSuccessRetrieveFileStream
215 rowProgressSuccessRetrieveFileStream)
216 {
217 toolStripStatusLabel1.Text =
218 $"Got {rowProgress.Row.Cells["NameColumn"].Value} file stream...";
219 toolStripProgressBar1.Value = rowProgress.Index + 1;
220  
221 statusStrip1.Update();
222  
223 var hash = (string)rowProgressSuccessRetrieveFileStream.Row.Cells["HashColumn"].Value;
224 var name = (string)rowProgressSuccessRetrieveFileStream.Row.Cells["NameColumn"].Value;
225  
226 var fileDescriptor = new VirtualFileDataObject.VirtualFileDataObject.FileDescriptor
227 {
228 Name = name,
229 StreamContents = stream =>
230 {
231 rowProgressSuccessRetrieveFileStream.MemoryStream.Seek(0, SeekOrigin.Begin);
232  
233 rowProgressSuccessRetrieveFileStream.MemoryStream.CopyTo(stream);
234 }
235 };
236  
237 fileDescriptors.Add(fileDescriptor);
238 }
239 });
240  
3 office 241 await Task.Run(() => RetrieveFileStream(rows, progress, _cancellationToken), _cancellationToken);
1 office 242  
3 office 243 if (_cancellationToken.IsCancellationRequested)
1 office 244 {
245 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
246 toolStripStatusLabel1.Text = "Done.";
247 }
248  
249 virtualFileDataObject.SetData(fileDescriptors);
250  
251 dataGridView1.DoDragDrop(virtualFileDataObject, DragDropEffects.Copy);
252 }
253 finally
254 {
255 Monitor.Exit(_mouseMoveLock);
256 }
257  
258 break;
259 }
260 }
261  
3 office 262 private async Task RetrieveFileStream(IReadOnlyList<DataGridViewRow> rows,
1 office 263 IProgress<DataGridViewRowProgress> progress,
264 CancellationToken cancellationToken)
265 {
266 var count = rows.Count;
267  
268 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
269 {
270 try
271 {
11 office 272 var fileStream = await _snapshotDatabase.RetrieveFileStreamAsync(
1 office 273 (string)rows[i].Cells["HashColumn"].Value,
274 cancellationToken);
275  
276 progress.Report(new DataGridViewRowProgressSuccessRetrieveFileStream(rows[i], i, fileStream));
277 }
278 catch (Exception exception)
279 {
280 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
281 }
282 }
283 }
284  
285 private void SnapshotManagerForm_Resize(object sender, EventArgs e)
286 {
287 if (_snapshotPreviewForm != null)
288 {
289 _snapshotPreviewForm.WindowState = WindowState;
290 }
291 }
292  
293 private void OpenInExplorerToolStripMenuItem_Click(object sender, EventArgs e)
294 {
295 var row = GetSelectedDataGridViewRows(dataGridView1).FirstOrDefault();
296 if (row == null)
297 {
298 return;
299 }
300  
301 Process.Start("explorer.exe", $"/select, \"{(string)row.Cells["PathColumn"].Value}\"");
302 }
303  
304 private async void DataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
305 {
306 var dataGridView = (DataGridView)sender;
307  
308 if (_snapshotPreviewForm == null)
309 {
310 _snapshotPreviewForm = new SnapshotPreviewForm(this, _snapshotDatabase);
311 _snapshotPreviewForm.Owner = this;
312 _snapshotPreviewForm.Closing += SnapshotPreviewForm_Closing;
313 _snapshotPreviewForm.Show();
314 }
315  
316 var row = GetSelectedDataGridViewRows(dataGridView).FirstOrDefault();
317 if (row == null)
318 {
319 return;
320 }
321  
322 var hash = (string)row.Cells["HashColumn"].Value;
323  
324 var snapshotPreview =
11 office 325 await Task.Run(async () => await _snapshotDatabase.RetrievePreviewAsync(hash, _cancellationToken),
1 office 326 _cancellationToken);
327  
328 if (snapshotPreview == null)
329 {
330 return;
331 }
332  
333 PreviewRetrieved?.Invoke(this, new PreviewRetrievedEventArgs(snapshotPreview));
334 }
335  
336 private void SnapshotPreviewForm_Closing(object sender, CancelEventArgs e)
337 {
338 if (_snapshotPreviewForm == null)
339 {
340 return;
341 }
342  
343 _snapshotPreviewForm.Dispose();
344 _snapshotPreviewForm = null;
345 }
346  
347 private async void NoneToolStripMenuItem_Click(object sender, EventArgs e)
348 {
349 var rows = GetSelectedDataGridViewRows(dataGridView1);
350  
351 var count = rows.Count;
352  
353 toolStripProgressBar1.Minimum = 0;
354 toolStripProgressBar1.Maximum = count;
355  
356 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
357 {
358 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
359 {
360 Log.Error(rowProgressFailure.Exception, "Failed to remove color from row.");
361  
362 toolStripStatusLabel1.Text =
363 $"Could not remove color from {rowProgress.Row.Cells["NameColumn"].Value}...";
364 toolStripProgressBar1.Value = rowProgress.Index + 1;
365  
366 statusStrip1.Update();
367  
368 return;
369 }
370  
371 rowProgress.Row.DefaultCellStyle.BackColor = Color.Empty;
372  
373 toolStripStatusLabel1.Text =
374 $"Removed color from {rowProgress.Row.Cells["NameColumn"].Value}...";
375 toolStripProgressBar1.Value = rowProgress.Index + 1;
376  
377 statusStrip1.Update();
378 });
379  
380 await Task.Run(() => RemoveColorFiles(rows, progress, _cancellationToken), _cancellationToken);
381  
3 office 382 if (_cancellationToken.IsCancellationRequested)
1 office 383 {
384 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
385 toolStripStatusLabel1.Text = "Done.";
386 }
387 }
388  
389 private async void ColorToolStripMenuItem_Click(object sender, EventArgs e)
390 {
391 var toolStripMenuItem = (ToolStripMenuItem)sender;
392 var color = toolStripMenuItem.BackColor;
393  
394 var rows = GetSelectedDataGridViewRows(dataGridView1);
395  
396 var count = rows.Count;
397  
398 toolStripProgressBar1.Minimum = 0;
399 toolStripProgressBar1.Maximum = count;
400  
401 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
402 {
403 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
404 {
405 Log.Error(rowProgressFailure.Exception, "Unable to color row.");
406  
407 toolStripStatusLabel1.Text =
408 $"Could not color {rowProgress.Row.Cells["NameColumn"].Value}...";
409 toolStripProgressBar1.Value = rowProgress.Index + 1;
410  
411 statusStrip1.Update();
412  
413 return;
414 }
415  
416 rowProgress.Row.DefaultCellStyle.BackColor = color;
417  
418 toolStripStatusLabel1.Text =
419 $"Colored {rowProgress.Row.Cells["NameColumn"].Value}...";
420 toolStripProgressBar1.Value = rowProgress.Index + 1;
421  
422 statusStrip1.Update();
423 });
424  
425 await Task.Run(() => ColorFiles(rows, color, progress, _cancellationToken), _cancellationToken);
426  
3 office 427 if (_cancellationToken.IsCancellationRequested)
1 office 428 {
429 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
430 toolStripStatusLabel1.Text = "Done.";
431 }
432 }
433  
434 private async void DeleteToolStripMenuItem_Click(object sender, EventArgs e)
435 {
436 var rows = GetSelectedDataGridViewRows(dataGridView1);
437  
438 var count = rows.Count;
439  
440 toolStripProgressBar1.Minimum = 0;
441 toolStripProgressBar1.Maximum = count;
442  
443 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
444 {
445 if (_cancellationToken.IsCancellationRequested)
446 {
447 return;
448 }
449  
450 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
451 {
452 Log.Error(rowProgressFailure.Exception, "Unable to delete row.");
453  
454 toolStripStatusLabel1.Text =
455 $"Could not remove {rowProgress.Row.Cells["NameColumn"].Value}...";
456 toolStripProgressBar1.Value = rowProgress.Index + 1;
457  
458 statusStrip1.Update();
459  
460 return;
461 }
462  
463 toolStripStatusLabel1.Text =
464 $"Removed {rowProgress.Row.Cells["NameColumn"].Value}...";
465 toolStripProgressBar1.Value = rowProgress.Index + 1;
466  
467 statusStrip1.Update();
468  
469 dataGridView1.Rows.Remove(rowProgress.Row);
470 });
471  
472 await Task.Run(() => DeleteFiles(rows, progress, _cancellationToken), _cancellationToken);
473  
3 office 474 if (_cancellationToken.IsCancellationRequested)
1 office 475 {
476 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
477 toolStripStatusLabel1.Text = "Done.";
478 }
479 }
480  
481 private async void DeleteFastToolStripMenuItem_Click(object sender, EventArgs e)
482 {
483 var rows = GetSelectedDataGridViewRows(dataGridView1);
484  
485 try
486 {
487 await DeleteFilesFast(rows, _cancellationToken);
488  
489 foreach (var row in rows)
490 {
491 dataGridView1.Rows.Remove(row);
492 }
493 }
494 catch (Exception exception)
495 {
496 Log.Error(exception, "Unable to remove rows.");
497 }
498 }
499  
500 private void SnapshotManager_SnapshotCreate(object sender, SnapshotCreateEventArgs e)
501 {
502 switch (e)
503 {
504 case SnapshotCreateSuccessEventArgs snapshotCreateSuccessEventArgs:
505 dataGridView1.InvokeIfRequired(dataGridView =>
506 {
507 var index = dataGridView.Rows.Add();
508  
509 dataGridView.Rows[index].Cells["TimeColumn"].Value =
510 DateTime.Parse(snapshotCreateSuccessEventArgs.Time);
511 dataGridView.Rows[index].Cells["NameColumn"].Value = snapshotCreateSuccessEventArgs.Name;
512 dataGridView.Rows[index].Cells["PathColumn"].Value = snapshotCreateSuccessEventArgs.Path;
513 dataGridView.Rows[index].Cells["HashColumn"].Value = snapshotCreateSuccessEventArgs.Hash;
514 dataGridView.Rows[index].DefaultCellStyle.BackColor = snapshotCreateSuccessEventArgs.Color;
515  
516 dataGridView.Sort(dataGridView.Columns["TimeColumn"], ListSortDirection.Descending);
517 });
518 break;
519 case SnapshotCreateFailureEventArgs snapshotCreateFailure:
520 Log.Warning(snapshotCreateFailure.Exception, "Could not create snapshot.");
521 break;
522 }
523 }
524  
525 private void RevertToThisToolStripMenuItem_Click(object sender, EventArgs e)
526 {
527 _mainForm.InvokeIfRequired(async form =>
528 {
529 var fileSystemWatchers = new List<FileSystemWatcherState>();
530 var watchPaths = new HashSet<string>();
531 // Temporary disable all filesystem watchers that are watching the selected file directory.
532 foreach (var row in GetSelectedDataGridViewRows(dataGridView1))
533 {
534 var path = (string)row.Cells["PathColumn"].Value;
535  
536 foreach (var fileSystemWatcher in form.FileSystemWatchers)
537 {
538 if (!path.IsPathEqual(fileSystemWatcher.Path) &&
539 !path.IsSubPathOf(fileSystemWatcher.Path))
540 {
541 continue;
542 }
543  
544 if (watchPaths.Contains(fileSystemWatcher.Path))
545 {
546 continue;
547 }
548  
549 fileSystemWatchers.Add(new FileSystemWatcherState(fileSystemWatcher));
550  
551 fileSystemWatcher.EnableRaisingEvents = false;
552  
553 watchPaths.Add(fileSystemWatcher.Path);
554 }
555 }
556  
557 try
558 {
559 var rows = GetSelectedDataGridViewRows(dataGridView1);
560  
561 var count = rows.Count;
562  
563 toolStripProgressBar1.Minimum = 0;
564 toolStripProgressBar1.Maximum = count;
565  
566 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
567 {
568 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
569 {
570 Log.Error(rowProgressFailure.Exception, "Could not revert to snapshot.");
571  
572 toolStripStatusLabel1.Text =
573 $"Could not revert {rowProgress.Row.Cells["NameColumn"].Value}...";
574 toolStripProgressBar1.Value = rowProgress.Index + 1;
575  
576 statusStrip1.Update();
577  
578 return;
579 }
580  
581 toolStripStatusLabel1.Text =
582 $"Reverted {rowProgress.Row.Cells["NameColumn"].Value}...";
583 toolStripProgressBar1.Value = rowProgress.Index + 1;
584  
585 statusStrip1.Update();
586 });
587  
588 await Task.Run(() => RevertFile(rows, progress, _cancellationToken), _cancellationToken);
589  
3 office 590 if (_cancellationToken.IsCancellationRequested)
1 office 591 {
592 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
593 toolStripStatusLabel1.Text = "Done.";
594 }
595 }
596 catch (Exception exception)
597 {
598 Log.Error(exception, "Could not update data grid view.");
599 }
600 finally
601 {
602 // Restore initial state.
603 foreach (var fileSystemWatcherState in fileSystemWatchers)
604 {
605 foreach (var fileSystemWatcher in form.FileSystemWatchers)
606 {
607 if (fileSystemWatcherState.FileSystemWatcher == fileSystemWatcher)
608 {
609 fileSystemWatcher.EnableRaisingEvents = fileSystemWatcherState.State;
610 }
611 }
612 }
613 }
614 });
615 }
616  
617 private async void SnapshotManagerForm_Load(object sender, EventArgs e)
618 {
12 office 619 // Start browsing for network services.
620 _horizonServiceBrowser.Browse("_horizon._tcp", "local");
621  
622 // Load snapshots.
1 office 623 toolStripProgressBar1.Minimum = 0;
11 office 624 toolStripProgressBar1.Maximum = (int)await _snapshotDatabase.CountSnapshotsAsync(_cancellationToken);
1 office 625  
626 var snapshotQueue = new ConcurrentQueue<Snapshot>();
627  
5 office 628 void IdleHandler(object idleHandlerSender, EventArgs idleHandlerArgs)
1 office 629 {
630 try
631 {
5 office 632 if (snapshotQueue.IsEmpty)
1 office 633 {
634 Application.Idle -= IdleHandler;
635  
636 dataGridView1.Sort(dataGridView1.Columns["TimeColumn"], ListSortDirection.Descending);
637 toolStripStatusLabel1.Text = "Done.";
5 office 638 }
1 office 639  
5 office 640 if (!snapshotQueue.TryDequeue(out var snapshot))
641 {
1 office 642 return;
643 }
644  
645 var index = dataGridView1.Rows.Add();
646  
647 dataGridView1.Rows[index].Cells["TimeColumn"].Value = DateTime.Parse(snapshot.Time);
648 dataGridView1.Rows[index].Cells["NameColumn"].Value = snapshot.Name;
649 dataGridView1.Rows[index].Cells["PathColumn"].Value = snapshot.Path;
650 dataGridView1.Rows[index].Cells["HashColumn"].Value = snapshot.Hash;
651 dataGridView1.Rows[index].DefaultCellStyle.BackColor = snapshot.Color;
652  
653 toolStripStatusLabel1.Text = $"Loaded {snapshot.Name}...";
654  
655 toolStripProgressBar1.Increment(1);
656  
657 statusStrip1.Update();
658 }
659 catch (Exception exception)
660 {
661 Log.Error(exception, "Could not update data grid view.");
662 }
663 }
5 office 664  
1 office 665 try
666 {
11 office 667 await foreach (var snapshot in _snapshotDatabase.LoadSnapshotsAsync(_cancellationToken).WithCancellation(_cancellationToken))
1 office 668 {
669 snapshotQueue.Enqueue(snapshot);
670 }
671  
5 office 672 Application.Idle += IdleHandler;
1 office 673 }
674 catch (Exception exception)
675 {
676 Application.Idle -= IdleHandler;
677  
678 Log.Error(exception, "Unable to load snapshots.");
679 }
680 }
681  
12 office 682 private void OnHorizonServiceBrowserOnServiceAdded(object o, Mono.Zeroconf.ServiceBrowseEventArgs args)
683 {
684 _resolvedService = args.Service;
685  
686 Log.Information("Found Service: {0}", _resolvedService.Name);
687  
688 _resolvedService.Resolved += OnServiceOnResolved;
689  
690 _resolvedService.Resolve();
691  
692 _resolvedService.Resolved -= OnServiceOnResolved;
693 }
694  
695 private void OnServiceOnResolved(object o, ServiceResolvedEventArgs args)
696 {
697 var service = (Service) args.Service;
698  
699 Log.Information("Resolved Service: {0} - {1}:{2} ({3} TXT record entries)", service.FullName, service.HostEntry.AddressList[0], service.UPort, service.TxtRecord.Count);
700  
701 _discoveredHorizonNetworkShares.TryAdd(service.Name, service);
702  
703 var discoveredServiceMenuItem = new ToolStripMenuItem(service.Name, null, OnShareWithNodeClicked);
704 discoveredServiceMenuItem.Tag = service;
705  
706 shareToToolStripMenuItem.DropDownItems.Add(discoveredServiceMenuItem);
707 }
708  
709 private async void OnShareWithNodeClicked(object sender, EventArgs e)
710 {
711 var toolStripMenuItem = (ToolStripMenuItem)sender;
712  
713 var service = (Service)toolStripMenuItem.Tag;
714  
715 var rows = GetSelectedDataGridViewRows(dataGridView1);
716  
717 var count = rows.Count;
718  
719 toolStripProgressBar1.Minimum = 0;
720 toolStripProgressBar1.Maximum = count;
721  
722 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
723 {
724 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
725 {
726 Log.Error(rowProgressFailure.Exception, "Unable to transfer data.");
727  
728 toolStripStatusLabel1.Text =
729 $"Could not transfer {rowProgress.Row.Cells["NameColumn"].Value}...";
730 toolStripProgressBar1.Value = rowProgress.Index + 1;
731  
732 statusStrip1.Update();
733  
734 return;
735 }
736  
737 toolStripStatusLabel1.Text =
738 $"Transferred {rowProgress.Row.Cells["NameColumn"].Value}...";
739 toolStripProgressBar1.Value = rowProgress.Index + 1;
740  
741 statusStrip1.Update();
742 });
743  
744 await Task.Run(() => TransferFiles(rows, service, progress, _cancellationToken), _cancellationToken);
745  
746 if (_cancellationToken.IsCancellationRequested)
747 {
748 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
749 toolStripStatusLabel1.Text = "Done.";
750 }
751 }
752  
1 office 753 private void SnapshotManagerForm_Closing(object sender, FormClosingEventArgs e)
754 {
755 _cancellationTokenSource.Cancel();
756  
757 if (_snapshotPreviewForm != null)
758 {
759 _snapshotPreviewForm.Close();
760 _snapshotPreviewForm = null;
761 }
762  
763 if (_hexViewForm != null)
764 {
765 _hexViewForm.Close();
766 _hexViewForm = null;
767 }
768 }
769  
770 private void DataGridView1_DragEnter(object sender, DragEventArgs e)
771 {
772 if (e.Data.GetDataPresent(DataFormats.FileDrop))
773 {
774 e.Effect = DragDropEffects.Copy;
775 return;
776 }
777  
778 e.Effect = DragDropEffects.None;
779 }
780  
7 office 781 private void CreateSnapshots(IReadOnlyList<string> files, Bitmap screenCapture, TrackedFolders.TrackedFolders trackedFolders, IProgress<CreateSnapshotProgress> progress, CancellationToken cancellationToken)
5 office 782 {
783 Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = 512 }, async file =>
784 {
785 var color = Color.Empty;
786 if (_mainForm.TrackedFolders.TryGet(file, out var folder))
787 {
788 color = folder.Color;
789 }
790  
791 var fileInfo = File.GetAttributes(file);
792 if (fileInfo.HasFlag(FileAttributes.Directory))
793 {
794 foreach (var directoryFile in Directory.GetFiles(file, "*.*", SearchOption.AllDirectories))
795 {
796 var name = Path.GetFileName(directoryFile);
797 var path = Path.Combine(Path.GetDirectoryName(directoryFile), name);
798  
799 try
800 {
11 office 801 await _snapshotDatabase.CreateSnapshotAsync(name, path, screenCapture, color,
5 office 802 _cancellationToken);
803  
804 progress.Report(new CreateSnapshotProgressSuccess(file));
805 }
806 catch (Exception exception)
807 {
808 progress.Report(new CreateSnapshotProgressFailure(file, exception));
809 }
810 }
811  
812 return;
813 }
814  
815 var fileName = Path.GetFileName(file);
816 var pathName = Path.Combine(Path.GetDirectoryName(file), fileName);
817  
818 try
819 {
11 office 820 await _snapshotDatabase.CreateSnapshotAsync(fileName, pathName, screenCapture, color,
5 office 821 _cancellationToken);
822  
823 progress.Report(new CreateSnapshotProgressSuccess(file));
824 }
825 catch (Exception exception)
826 {
827 progress.Report(new CreateSnapshotProgressFailure(file, exception));
828 }
829 });
830 }
1 office 831 private async void DataGridView1_DragDrop(object sender, DragEventArgs e)
832 {
833 if (!e.Data.GetDataPresent(DataFormats.FileDrop))
834 {
835 return;
836 }
837  
838 var files = (string[])e.Data.GetData(DataFormats.FileDrop);
839  
840 toolStripProgressBar1.Minimum = 0;
841 toolStripProgressBar1.Maximum = files.Length;
842 toolStripStatusLabel1.Text = "Snapshotting files...";
843  
844 var screenCapture = ScreenCapture.Capture((CaptureMode)_mainForm.Configuration.CaptureMode);
5 office 845  
846 var progress = new Progress<CreateSnapshotProgress>(createSnapshotProgress =>
1 office 847 {
5 office 848 switch (createSnapshotProgress)
849 {
850 case CreateSnapshotProgressSuccess createSnapshotProgressSuccess:
851 toolStripStatusLabel1.Text = $"Snapshot taken of {createSnapshotProgressSuccess.File}.";
852 break;
853 case CreateSnapshotProgressFailure createSnapshotProgressFailure:
854 if (createSnapshotProgressFailure.Exception is SQLiteException { ResultCode: SQLiteErrorCode.Constraint })
855 {
856 toolStripStatusLabel1.Text = $"Snapshot of file {createSnapshotProgressFailure.File} already exists.";
857 break;
858 }
859  
860 toolStripStatusLabel1.Text = $"Could not snapshot file {createSnapshotProgressFailure.File}";
861 Log.Warning(createSnapshotProgressFailure.Exception, $"Could not snapshot file {createSnapshotProgressFailure.File}");
862 break;
863 }
864  
865 toolStripProgressBar1.Increment(1);
866 statusStrip1.Update();
867 });
868  
11 office 869 await Task.Factory.StartNew( () =>
5 office 870 {
7 office 871 CreateSnapshots(files, screenCapture, _mainForm.TrackedFolders, progress, _cancellationToken);
5 office 872 }, _cancellationToken);
1 office 873 }
874  
875 private async void FileToolStripMenuItem_Click(object sender, EventArgs e)
876 {
877 var dialog = new CommonOpenFileDialog();
878 if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
879 {
880 var screenCapture = ScreenCapture.Capture((CaptureMode)_mainForm.Configuration.CaptureMode);
881  
882 var fileName = Path.GetFileName(dialog.FileName);
883 var directory = Path.GetDirectoryName(dialog.FileName);
884 var pathName = Path.Combine(directory, fileName);
885  
886 var color = Color.Empty;
887 if (_mainForm.TrackedFolders.TryGet(directory, out var folder))
888 {
889 color = folder.Color;
890 }
891  
892 try
893 {
11 office 894 await _snapshotDatabase.CreateSnapshotAsync(fileName, pathName, screenCapture, color,
1 office 895 _cancellationToken);
896 }
897 catch (SQLiteException exception)
898 {
899 if (exception.ResultCode == SQLiteErrorCode.Constraint)
900 {
901 Log.Information(exception, "Snapshot already exists.");
902 }
903 }
904 catch (Exception exception)
905 {
906 Log.Warning(exception, "Could not create snapshot.");
907 }
908 }
909 }
910  
911 private async void DirectoryToolStripMenuItem_Click(object sender, EventArgs e)
912 {
913 var dialog = new CommonOpenFileDialog { IsFolderPicker = true };
914 if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
915 {
916 var screenCapture = ScreenCapture.Capture((CaptureMode)_mainForm.Configuration.CaptureMode);
917 foreach (var directoryFile in Directory.GetFiles(dialog.FileName, "*.*", SearchOption.AllDirectories))
918 {
919 var name = Path.GetFileName(directoryFile);
920 var directory = Path.GetDirectoryName(directoryFile);
921 var path = Path.Combine(directory, name);
922  
923 var color = Color.Empty;
924 if (_mainForm.TrackedFolders.TryGet(directory, out var folder))
925 {
926 color = folder.Color;
927 }
928  
929 try
930 {
11 office 931 await _snapshotDatabase.CreateSnapshotAsync(name, path, screenCapture, color, _cancellationToken);
1 office 932 }
933 catch (SQLiteException exception)
934 {
935 if (exception.ResultCode == SQLiteErrorCode.Constraint)
936 {
937 Log.Information(exception, "Snapshot already exists.");
938 }
939 }
940 catch (Exception exception)
941 {
942 Log.Warning(exception, "Could not create snapshot.");
943 }
944 }
945 }
946 }
947  
948 private async void RelocateToolStripMenuItem_Click(object sender, EventArgs e)
949 {
950 var commonOpenFileDialog = new CommonOpenFileDialog
951 {
952 InitialDirectory = _mainForm.Configuration.LastFolder,
953 IsFolderPicker = true
954 };
955  
956 if (commonOpenFileDialog.ShowDialog() != CommonFileDialogResult.Ok)
957 {
958 return;
959 }
960  
961 _mainForm.Configuration.LastFolder = commonOpenFileDialog.FileName;
962 _mainForm.ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
963 async () => await _mainForm.SaveConfiguration(), _cancellationToken);
964  
965 var directory = commonOpenFileDialog.FileName;
966  
967 var rows = GetSelectedDataGridViewRows(dataGridView1);
968  
969 var count = rows.Count;
970  
971 toolStripProgressBar1.Minimum = 0;
972 toolStripProgressBar1.Maximum = count;
973  
974 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
975 {
976 var path = Path.Combine(directory,
977 (string)rowProgress.Row.Cells["NameColumn"].Value);
978  
979 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
980 {
981 Log.Error(rowProgressFailure.Exception, "Could not relocate snapshot.");
982  
983 toolStripStatusLabel1.Text =
984 $"Could not relocate {rowProgress.Row.Cells["NameColumn"].Value} to {path}...";
985 toolStripProgressBar1.Value = rowProgress.Index + 1;
986  
987 statusStrip1.Update();
988  
989 return;
990 }
991  
992 rowProgress.Row.Cells["PathColumn"].Value = path;
993  
994 toolStripStatusLabel1.Text =
995 $"Relocated {rowProgress.Row.Cells["NameColumn"].Value} to {path}...";
996 toolStripProgressBar1.Value = rowProgress.Index + 1;
997  
998 statusStrip1.Update();
999 });
1000  
1001 await Task.Run(() => RelocateFiles(rows, directory, progress, _cancellationToken), _cancellationToken);
1002  
3 office 1003 if (_cancellationToken.IsCancellationRequested)
1 office 1004 {
1005 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
1006 toolStripStatusLabel1.Text = "Done.";
1007 }
1008 }
1009  
1010 private async void NoteToolStripMenuItem_Click(object sender, EventArgs e)
1011 {
1012 if (_snapshotNote != null)
1013 {
1014 return;
1015 }
1016  
1017 var row = GetSelectedDataGridViewRows(dataGridView1).FirstOrDefault();
1018 if (row == null)
1019 {
1020 return;
1021 }
1022  
1023 try
1024 {
11 office 1025 var snapshotPreview = await _snapshotDatabase.RetrievePreviewAsync(
1 office 1026 (string)row.Cells["HashColumn"].Value, _cancellationToken);
1027  
1028 if (snapshotPreview == null)
1029 {
1030 return;
1031 }
1032  
1033 _snapshotNote = new SnapshotNoteForm(this, snapshotPreview);
1034 _snapshotNote.Owner = this;
1035 _snapshotNote.SaveNote += SnapshotNote_SaveNote;
1036 _snapshotNote.Closing += SnapshotNote_Closing;
1037 _snapshotNote.Show();
1038 }
1039 catch (Exception exception)
1040 {
1041 Log.Error(exception, "Could not open notes form.");
1042 }
1043 }
1044  
1045 private async void SnapshotNote_SaveNote(object sender, SaveNoteEventArgs e)
1046 {
1047 var rows = GetSelectedDataGridViewRows(dataGridView1);
1048  
1049 var count = rows.Count;
1050  
1051 toolStripProgressBar1.Minimum = 0;
1052 toolStripProgressBar1.Maximum = count;
1053  
1054 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
1055 {
1056 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
1057 {
1058 Log.Error(rowProgressFailure.Exception, "Could not update note for snapshot.");
1059  
1060 toolStripStatusLabel1.Text =
1061 $"Could not update note for {rowProgress.Row.Cells["NameColumn"].Value}...";
1062 toolStripProgressBar1.Value = rowProgress.Index + 1;
1063  
1064 statusStrip1.Update();
1065  
1066 return;
1067 }
1068  
1069 toolStripStatusLabel1.Text =
1070 $"Updated note for {rowProgress.Row.Cells["NameColumn"].Value}...";
1071 toolStripProgressBar1.Value = rowProgress.Index + 1;
1072  
1073 statusStrip1.Update();
1074 });
1075  
1076 await Task.Run(() => UpdateNote(rows, e.Note, progress, _cancellationToken), _cancellationToken);
1077  
3 office 1078 if (_cancellationToken.IsCancellationRequested)
1 office 1079 {
1080 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
1081 toolStripStatusLabel1.Text = "Done.";
1082 }
1083 }
1084  
1085 private void SnapshotNote_Closing(object sender, CancelEventArgs e)
1086 {
1087 if (_snapshotNote == null)
1088 {
1089 return;
1090 }
1091  
1092 _snapshotNote.Closing -= SnapshotNote_Closing;
1093 _snapshotNote.Close();
1094 _snapshotNote = null;
1095 }
1096  
1097 private async void ViewHexToolStripMenuItem_Click(object sender, EventArgs e)
1098 {
1099 var rows = GetSelectedDataGridViewRows(dataGridView1);
1100 var row = rows.FirstOrDefault();
1101 if (row == null)
1102 {
1103 return;
1104 }
1105  
1106 var hash = (string)row.Cells["HashColumn"].Value;
1107  
11 office 1108 using (var memoryStream = await _snapshotDatabase.RetrieveFileStreamAsync(hash, _cancellationToken))
1 office 1109 {
1110 if (memoryStream == null)
1111 {
1112 return;
1113 }
1114  
1115 if (_hexViewForm != null)
1116 {
1117 _hexViewForm.UpdateData(memoryStream.ToArray());
1118 _hexViewForm.Activate();
1119 return;
1120 }
1121  
1122 _hexViewForm = new HexViewForm(_snapshotDatabase, hash, memoryStream.ToArray());
1123 _hexViewForm.Owner = this;
1124 _hexViewForm.Closing += HexViewForm_Closing;
1125 _hexViewForm.SaveData += HexViewForm_SaveData;
1126  
1127 _hexViewForm.Show();
1128 }
1129 }
1130  
1131 private async void HexViewForm_SaveData(object sender, SaveDataEventArgs e)
1132 {
11 office 1133 var hash = await _snapshotDatabase.UpdateFileAsync(e.Hash, e.Data, _cancellationToken);
1 office 1134  
1135 if (string.IsNullOrEmpty(hash))
1136 {
1137 return;
1138 }
1139  
1140 dataGridView1.InvokeIfRequired(dataGridView =>
1141 {
1142 // Update the hash in the datagridview.
1143 var removeRows = new List<DataGridViewRow>();
1144 foreach (var row in dataGridView.Rows.OfType<DataGridViewRow>())
1145 {
1146 if ((string)row.Cells["HashColumn"].Value == hash)
1147 {
1148 removeRows.Add(row);
1149 }
1150  
1151 if ((string)row.Cells["HashColumn"].Value != e.Hash)
1152 {
1153 continue;
1154 }
1155  
1156 row.Cells["HashColumn"].Value = hash;
1157 }
1158  
1159 // Remove rows that might have the same hash.
1160 foreach (var row in removeRows)
1161 {
1162 dataGridView.Rows.Remove(row);
1163 }
1164 });
1165 }
1166  
1167 private void HexViewForm_Closing(object sender, CancelEventArgs e)
1168 {
1169 if (_hexViewForm == null)
1170 {
1171 return;
1172 }
1173  
1174 _hexViewForm.SaveData -= HexViewForm_SaveData;
1175 _hexViewForm.Closing -= HexViewForm_Closing;
1176 _hexViewForm.Close();
1177 _hexViewForm = null;
1178 }
1179  
1180 private async void FileToolStripMenuItem2_Click(object sender, EventArgs e)
1181 {
1182 var commonOpenFileDialog = new CommonOpenFileDialog
1183 {
1184 InitialDirectory = _mainForm.Configuration.LastFolder,
1185 IsFolderPicker = true
1186 };
1187  
1188 if (commonOpenFileDialog.ShowDialog() == CommonFileDialogResult.Ok)
1189 {
1190 _mainForm.Configuration.LastFolder = commonOpenFileDialog.FileName;
1191 _mainForm.ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
1192 async () => await _mainForm.SaveConfiguration(), _cancellationToken);
1193  
1194 var directory = commonOpenFileDialog.FileName;
1195  
1196 var rows = GetSelectedDataGridViewRows(dataGridView1);
1197  
1198 var count = rows.Count;
1199  
1200 toolStripProgressBar1.Minimum = 0;
1201 toolStripProgressBar1.Maximum = count;
1202  
1203 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
1204 {
1205 var fileInfo =
1206 new FileInfo((string)rowProgress.Row.Cells["NameColumn"].Value);
1207 var file = fileInfo.Name;
1208 var path = Path.Combine(directory, file);
1209  
1210 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
1211 {
1212 Log.Error(rowProgressFailure.Exception, "Could not save snapshot.");
1213  
1214 toolStripStatusLabel1.Text =
1215 $"Could not save snapshot {rowProgress.Row.Cells["NameColumn"].Value} to {path}...";
1216 toolStripProgressBar1.Value = rowProgress.Index + 1;
1217  
1218 statusStrip1.Update();
1219  
1220 return;
1221 }
1222  
1223 toolStripStatusLabel1.Text =
1224 $"Saved {rowProgress.Row.Cells["NameColumn"].Value} to {path}...";
1225 toolStripProgressBar1.Value = rowProgress.Index + 1;
1226  
1227 statusStrip1.Update();
1228 });
1229  
1230 await Task.Run(() => SaveFilesTo(rows, directory, progress, _cancellationToken), _cancellationToken);
1231  
3 office 1232 if (_cancellationToken.IsCancellationRequested)
1 office 1233 {
1234 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
1235 toolStripStatusLabel1.Text = "Done.";
1236 }
1237 }
1238 }
1239  
1240 private async void DirectoryToolStripMenuItem2_Click(object sender, EventArgs e)
1241 {
1242 var select = GetSelectedDataGridViewRows(dataGridView1).FirstOrDefault();
1243  
1244 if (select == null)
1245 {
1246 return;
1247 }
1248  
1249 // C:\aa\bbb\dd.txt
1250 var path = (string)select.Cells["PathColumn"].Value;
1251  
1252 // C:\aa\bbb\
1253 var basePath = Path.GetDirectoryName(path);
1254  
1255 var dialog = new CommonOpenFileDialog { IsFolderPicker = true };
1256 if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
1257 {
1258 //Log.Information(dialog.FileName);
1259 var rows = GetAllDataGridViewRows(dataGridView1);
1260 var count = rows.Count;
1261  
1262 toolStripProgressBar1.Minimum = 0;
1263 toolStripProgressBar1.Maximum = count;
1264 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
1265 {
1266 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
1267 {
1268 Log.Error(rowProgressFailure.Exception, "Could not save file.");
1269  
1270 toolStripStatusLabel1.Text =
1271 $"Could not save file {rowProgress.Row.Cells["NameColumn"].Value} to {basePath}...";
1272 toolStripProgressBar1.Value = rowProgress.Index + 1;
1273  
1274 statusStrip1.Update();
1275  
1276 return;
1277 }
1278  
1279 toolStripStatusLabel1.Text =
1280 $"Saved {rowProgress.Row.Cells["NameColumn"].Value} to {basePath}...";
1281 toolStripProgressBar1.Value = rowProgress.Index + 1;
1282  
1283 statusStrip1.Update();
1284 });
1285  
1286 await Task.Run(() => SaveDirectoryTo(rows, basePath, dialog.FileName, progress, _cancellationToken),
1287 _cancellationToken);
1288  
3 office 1289 if (_cancellationToken.IsCancellationRequested)
1 office 1290 {
1291 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
1292 toolStripStatusLabel1.Text = "Done.";
1293 }
1294 }
1295 }
1296  
1297 private void TextBox1_TextChanged(object sender, EventArgs e)
1298 {
1299 _searchTextBoxChangedContinuation.Schedule(TimeSpan.FromSeconds(1), () =>
1300 {
1301 textBox1.InvokeIfRequired(textBox =>
1302 {
1303 var search = textBox.Text;
1304  
1305 dataGridView1.InvokeIfRequired(dataGridView =>
1306 {
1307 foreach (var row in GetAllDataGridViewRows(dataGridView))
1308 {
1309 if(row.Cells["PathColumn"].Value == null)
1310 {
1311 continue;
1312 }
1313  
1314 switch (((string)row.Cells["PathColumn"].Value).IndexOf(search,
1315 StringComparison.OrdinalIgnoreCase))
1316 {
1317 case -1:
1318 row.Visible = false;
1319 break;
1320 default:
1321 row.Visible = true;
1322 break;
1323 }
1324 }
1325 });
1326 });
1327 }, _cancellationToken);
1328 }
1329  
1330 private async void RecomputeHashesToolStripMenuItem1_Click(object sender, EventArgs e)
1331 {
1332 var rows = GetSelectedDataGridViewRows(dataGridView1);
1333  
1334 var count = rows.Count;
1335  
1336 toolStripProgressBar1.Minimum = 0;
1337 toolStripProgressBar1.Maximum = count;
1338  
1339 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
1340 {
1341 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
1342 {
1343 Log.Error(rowProgressFailure.Exception, "Could not recompute hash for snapshot.");
1344  
1345 toolStripStatusLabel1.Text =
1346 $"Could not recompute hash for {rowProgress.Row.Cells["NameColumn"].Value}...";
1347 toolStripProgressBar1.Value = rowProgress.Index + 1;
1348  
1349 statusStrip1.Update();
1350  
1351 return;
1352 }
1353  
1354 toolStripStatusLabel1.Text =
1355 $"Recomputed hash for {rowProgress.Row.Cells["NameColumn"].Value}...";
1356 toolStripProgressBar1.Value = rowProgress.Index + 1;
1357  
1358 statusStrip1.Update();
1359 });
1360  
1361 await Task.Run(() => RecomputeHashes(rows, progress, _cancellationToken), _cancellationToken);
1362  
3 office 1363 if (_cancellationToken.IsCancellationRequested)
1 office 1364 {
1365 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
1366 toolStripStatusLabel1.Text = "Done.";
1367 }
1368 }
1369  
1370 private async void NormalizeDateTimeToolStripMenuItem_Click(object sender, EventArgs e)
1371 {
1372 var rows = GetSelectedDataGridViewRows(dataGridView1);
1373  
1374 var count = rows.Count;
1375  
1376 toolStripProgressBar1.Minimum = 0;
1377 toolStripProgressBar1.Maximum = count;
1378  
1379 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
1380 {
1381 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
1382 {
1383 Log.Error(rowProgressFailure.Exception, "Could not normalize date-time for snapshot.");
1384  
1385 toolStripStatusLabel1.Text =
1386 $"Could not normalize date-time for {rowProgress.Row.Cells["NameColumn"].Value}...";
1387 toolStripProgressBar1.Value = rowProgress.Index + 1;
1388  
1389 statusStrip1.Update();
1390  
1391 return;
1392 }
1393  
1394 toolStripStatusLabel1.Text =
1395 $"Normalized date-time for {rowProgress.Row.Cells["NameColumn"].Value}...";
1396 toolStripProgressBar1.Value = rowProgress.Index + 1;
1397  
1398 statusStrip1.Update();
1399 });
1400  
1401 await Task.Run(() => NormalizeDateTime(rows, progress, _cancellationToken), _cancellationToken);
1402  
3 office 1403 if (_cancellationToken.IsCancellationRequested)
1 office 1404 {
1405 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
1406 toolStripStatusLabel1.Text = "Done.";
1407 }
1408 }
1409  
1410 #endregion
1411  
1412 #region Private Methods
12 office 1413 private void DataGridView1_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
1414 {
1415 DataGridView dataGridView = sender as DataGridView;
1416 foreach (DataGridViewCell cell in dataGridView.Rows[e.RowIndex].Cells)
1417 {
1418 if (cell.Selected == false) { continue; }
1419 var bgColorCell = Color.White;
1420 if (cell.Style.BackColor != Color.Empty) { bgColorCell = cell.Style.BackColor; }
1421 else if (cell.InheritedStyle.BackColor != Color.Empty) { bgColorCell = cell.InheritedStyle.BackColor; }
1422 cell.Style.SelectionBackColor = MixColor(bgColorCell, Color.FromArgb(0, 150, 255), 10, 4);
1423 }
1424 }
1 office 1425  
12 office 1426 //Mix two colors
1427 //Example: Steps=10 & Position=4 makes Color2 mix 40% into Color1
1428 /// <summary>
1429 /// Mix two colors.
1430 /// </summary>
1431 /// <param name="Color1"></param>
1432 /// <param name="Color2"></param>
1433 /// <param name="Steps"></param>
1434 /// <param name="Position"></param>
1435 /// <example>Steps=10 & Positon=4 makes Color2 mix 40% into Color1</example>
1436 /// <remarks>https://stackoverflow.com/questions/38337849/transparent-selectionbackcolor-for-datagridview-cell</remarks>
1437 /// <returns></returns>
1438 public static Color MixColor(Color Color1, Color Color2, int Steps, int Position)
1439 {
1440 if (Position <= 0 || Steps <= 1) { return Color1; }
1441 if (Position >= Steps) { return Color2; }
1442 return Color.FromArgb(
1443 Color1.R + ((Color2.R - Color1.R) / Steps * Position),
1444 Color1.G + ((Color2.G - Color1.G) / Steps * Position),
1445 Color1.B + ((Color2.B - Color1.B) / Steps * Position)
1446 );
1447 }
1448  
1 office 1449 private async Task DeleteFiles(IReadOnlyList<DataGridViewRow> rows, IProgress<DataGridViewRowProgress> progress,
1450 CancellationToken cancellationToken)
1451 {
1452 var count = rows.Count;
1453  
1454 for (var index = 0; index < count && !cancellationToken.IsCancellationRequested; ++index)
1455 {
1456 try
1457 {
11 office 1458 await _snapshotDatabase.RemoveFileAsync((string)rows[index].Cells["HashColumn"].Value,
1 office 1459 cancellationToken);
1460  
1461 progress.Report(new DataGridViewRowProgressSuccess(rows[index], index));
1462 }
1463 catch (Exception exception)
1464 {
1465 progress.Report(new DataGridViewRowProgressFailure(rows[index], index, exception));
1466 }
1467 }
1468 }
1469  
1470 private async Task DeleteFilesFast(IReadOnlyList<DataGridViewRow> rows, CancellationToken cancellationToken)
1471 {
1472 var hashes = rows.Select(row => (string)row.Cells["HashColumn"].Value);
1473  
11 office 1474 await _snapshotDatabase.RemoveFileFastAsync(hashes, cancellationToken);
1 office 1475 }
1476  
1477 private async Task UpdateNote(IReadOnlyList<DataGridViewRow> rows, string note,
1478 IProgress<DataGridViewRowProgress> progress, CancellationToken cancellationToken)
1479 {
1480 var count = rows.Count;
1481  
1482 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1483 {
1484 try
1485 {
11 office 1486 await _snapshotDatabase.UpdateNoteAsync((string)rows[i].Cells["HashColumn"].Value, note,
1 office 1487 cancellationToken);
1488  
1489 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1490 }
1491 catch (Exception exception)
1492 {
1493 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1494 }
1495 }
1496 }
1497  
1498 private static List<DataGridViewRow> GetSelectedDataGridViewRows(DataGridView dataGridView)
1499 {
1500 return dataGridView.SelectedRows.OfType<DataGridViewRow>().ToList();
1501 }
1502  
1503 private static List<DataGridViewRow> GetAllDataGridViewRows(DataGridView dataGridView)
1504 {
1505 return dataGridView.Rows.OfType<DataGridViewRow>().ToList();
1506 }
1507  
1508 private async Task RemoveColorFiles(IReadOnlyList<DataGridViewRow> rows,
1509 IProgress<DataGridViewRowProgress> progress,
1510 CancellationToken cancellationToken)
1511 {
1512 var count = rows.Count;
1513  
1514 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1515 {
1516 try
1517 {
11 office 1518 await _snapshotDatabase.RemoveColorAsync((string)rows[i].Cells["HashColumn"].Value,
1 office 1519 cancellationToken);
1520  
1521 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1522 }
1523 catch (Exception exception)
1524 {
1525 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1526 }
1527 }
1528 }
1529  
1530 private async Task ColorFiles(IReadOnlyList<DataGridViewRow> rows, Color color,
1531 IProgress<DataGridViewRowProgress> progress, CancellationToken cancellationToken)
1532 {
1533 var count = rows.Count;
1534  
1535 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1536 {
1537 try
1538 {
11 office 1539 await _snapshotDatabase.UpdateColorAsync((string)rows[i].Cells["HashColumn"].Value, color,
1 office 1540 cancellationToken);
1541  
1542 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1543 }
1544 catch (Exception exception)
1545 {
1546 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1547 }
1548 }
1549 }
1550  
12 office 1551  
1552 private async Task TransferFiles(IReadOnlyList<DataGridViewRow> rows, Service service,
1553 IProgress<DataGridViewRowProgress> progress, CancellationToken cancellationToken)
1554 {
1555  
1556 var client = new WatsonTcpClient($"{service.HostEntry.AddressList[0]}", service.UPort);
1557 client.Events.MessageReceived += (sender, args) =>
1558 {
1559 Log.Information($"{args.Data.Length} byte long message received from {args.Client.IpPort}");
1560 };
1561  
1562 try
1563 {
1564 client.Connect();
1565  
1566 var count = rows.Count;
1567  
1568 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1569 {
1570 try
1571 {
1572 var completeSnapshot = await _snapshotDatabase.GetCompleteSnapshot((string)rows[i].Cells["HashColumn"].Value, cancellationToken);
1573  
1574 var jsonSnapshot = JsonConvert.SerializeObject(completeSnapshot);
1575  
1576 await client.SendAsync(jsonSnapshot, null, cancellationToken);
1577  
1578 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1579 }
1580 catch (Exception exception)
1581 {
1582 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1583 }
1584 }
1585 }
1586 finally
1587 {
1588 client.Dispose();
1589 }
1590  
1591 }
1592  
1 office 1593 private async Task RevertFile(IReadOnlyList<DataGridViewRow> rows, IProgress<DataGridViewRowProgress> progress,
1594 CancellationToken cancellationToken)
1595 {
1596 var count = rows.Count;
1597  
1598 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1599 {
1600 try
1601 {
11 office 1602 await _snapshotDatabase.RevertFileAsync((string)rows[i].Cells["NameColumn"].Value,
1 office 1603 (string)rows[i].Cells["HashColumn"].Value,
1604 cancellationToken, _mainForm.Configuration.AtomicOperations);
1605  
1606 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1607 }
1608 catch (Exception exception)
1609 {
1610 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1611 }
1612 }
1613 }
1614  
1615 private async void SaveFilesTo(IReadOnlyList<DataGridViewRow> rows, string directory,
1616 IProgress<DataGridViewRowProgress> progress, CancellationToken cancellationToken)
1617 {
1618 var count = rows.Count;
1619  
1620 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1621 {
1622 try
1623 {
1624 var fileInfo = new FileInfo((string)rows[i].Cells["NameColumn"].Value);
1625 var file = fileInfo.Name;
1626 var path = Path.Combine(directory, file);
1627  
11 office 1628 await _snapshotDatabase.SaveFileAsync(path, (string)rows[i].Cells["HashColumn"].Value,
1 office 1629 cancellationToken);
1630  
1631 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1632 }
1633 catch (Exception exception)
1634 {
1635 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1636 }
1637 }
1638 }
1639  
1640 private async Task RelocateFiles(IReadOnlyList<DataGridViewRow> rows, string directory,
1641 IProgress<DataGridViewRowProgress> progress,
1642 CancellationToken cancellationToken)
1643 {
1644 var count = rows.Count;
1645  
1646 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1647 {
1648 try
1649 {
1650 var path = Path.Combine(directory, (string)rows[i].Cells["NameColumn"].Value);
1651  
11 office 1652 await _snapshotDatabase.RelocateFileAsync((string)rows[i].Cells["HashColumn"].Value, path,
1 office 1653 cancellationToken);
1654  
1655 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1656 }
1657 catch (Exception exception)
1658 {
1659 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1660 }
1661 }
1662 }
1663  
1664 private async void RecomputeHashes(IReadOnlyList<DataGridViewRow> rows,
1665 IProgress<DataGridViewRowProgress> progress,
1666 CancellationToken cancellationToken)
1667 {
1668 var count = rows.Count;
1669  
1670 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1671 {
1672 try
1673 {
1674 using (var memoryStream =
11 office 1675 await _snapshotDatabase.RetrieveFileStreamAsync((string)rows[i].Cells["HashColumn"].Value,
1 office 1676 cancellationToken))
1677 {
1678 if (memoryStream == null)
1679 {
1680 continue;
1681 }
1682  
1683 using (var md5 = MD5.Create())
1684 {
1685 var recomputedHash = md5.ComputeHash(memoryStream);
1686 var hashHex = BitConverter.ToString(recomputedHash).Replace("-", "")
1687 .ToLowerInvariant();
1688  
11 office 1689 await _snapshotDatabase.UpdateHashAsync((string)rows[i].Cells["HashColumn"].Value, hashHex,
1 office 1690 cancellationToken);
1691  
1692 rows[i].Cells["HashColumn"].Value = hashHex;
1693  
1694 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1695 }
1696 }
1697 }
1698 catch (Exception exception)
1699 {
1700 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1701 }
1702 }
1703 }
1704  
1705 private async Task SaveDirectoryTo(IReadOnlyList<DataGridViewRow> rows, string basePath, string targetPath,
1706 IProgress<DataGridViewRowProgress> progress,
1707 CancellationToken cancellationToken)
1708 {
1709 var store = new HashSet<string>();
1710  
1711 var count = rows.Count;
1712  
1713 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1714 {
1715 try
1716 {
1717 // C:\aa\bbb\fff\gg.txt
1718 var rowPath = (string)rows[i].Cells["PathColumn"].Value;
1719 if (store.Contains(rowPath))
1720 {
1721 continue;
1722 }
1723  
1724 // C:\aa\bbb\fff\gg.txt subpath C:\aa\bbb\
1725 if (!rowPath.IsPathEqual(basePath) &&
1726 !rowPath.IsSubPathOf(basePath))
1727 {
1728 continue;
1729 }
1730  
1731 var rootPath = new DirectoryInfo(basePath).Name;
1732 var relPath = rowPath.Remove(0, basePath.Length).Trim('\\');
1733 var newPath = Path.Combine(targetPath, rootPath, relPath);
1734  
1735 var hash = (string)rows[i].Cells["HashColumn"].Value;
11 office 1736 await _snapshotDatabase.SaveFileAsync(newPath, hash, cancellationToken);
1 office 1737  
1738 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1739  
1740 if (!store.Contains(rowPath))
1741 {
1742 store.Add(rowPath);
1743 }
1744 }
1745 catch (Exception exception)
1746 {
1747 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1748 }
1749 }
1750 }
1751  
1752 private async Task NormalizeDateTime(IReadOnlyList<DataGridViewRow> rows,
1753 IProgress<DataGridViewRowProgress> progress,
1754 CancellationToken cancellationToken)
1755 {
1756 var count = rows.Count;
1757  
1758 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1759 {
1760 try
1761 {
11 office 1762 await _snapshotDatabase.NormalizeTimeAsync((string)rows[i].Cells["HashColumn"].Value,
1 office 1763 cancellationToken);
1764  
1765 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1766 }
1767 catch (Exception exception)
1768 {
1769 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1770 }
1771 }
1772 }
1773  
1774 private async void deleteToolStripMenuItem1_Click(object sender, EventArgs e)
1775 {
1776 var toolStripMenuItem = (ToolStripMenuItem)sender;
1777  
1778 var rows = GetSelectedDataGridViewRows(dataGridView1);
1779  
1780 var count = rows.Count;
1781  
1782 toolStripProgressBar1.Minimum = 0;
1783 toolStripProgressBar1.Maximum = count;
1784  
1785 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
1786 {
1787 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
1788 {
1789 Log.Error(rowProgressFailure.Exception, "Unable to delete screenshot.");
1790  
1791 toolStripStatusLabel1.Text =
1792 $"Could not delete screenshot for {rowProgress.Row.Cells["NameColumn"].Value}...";
1793 toolStripProgressBar1.Value = rowProgress.Index + 1;
1794  
1795 statusStrip1.Update();
1796  
1797 return;
1798 }
1799  
1800 toolStripStatusLabel1.Text =
1801 $"Colored {rowProgress.Row.Cells["NameColumn"].Value}...";
1802 toolStripProgressBar1.Value = rowProgress.Index + 1;
1803  
1804 statusStrip1.Update();
1805 });
1806  
1807 await Task.Run(() => DeleteScreenshots(rows, progress, _cancellationToken), _cancellationToken);
1808  
3 office 1809 if (_cancellationToken.IsCancellationRequested)
1 office 1810 {
1811 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
1812 toolStripStatusLabel1.Text = "Done.";
1813 }
1814 }
1815  
1816 private async Task DeleteScreenshots(IReadOnlyList<DataGridViewRow> rows,
1817 IProgress<DataGridViewRowProgress> progress,
1818 CancellationToken cancellationToken)
1819 {
1820 var count = rows.Count;
1821  
1822 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1823 {
1824 try
1825 {
11 office 1826 await _snapshotDatabase.DeleteScreenshotAsync((string)rows[i].Cells["HashColumn"].Value,
1 office 1827 cancellationToken);
1828  
1829 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1830 }
1831 catch (Exception exception)
1832 {
1833 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1834 }
1835 }
1836 }
1837  
1838 #endregion
1839 }
1840 }