Horizon – Blame information for rev 20

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  
14 office 701 // Do not add discovered services more than once.
702 if (!_discoveredHorizonNetworkShares.TryAdd(service.Name, service))
703 {
704 return;
705 }
12 office 706  
707 var discoveredServiceMenuItem = new ToolStripMenuItem(service.Name, null, OnShareWithNodeClicked);
708 discoveredServiceMenuItem.Tag = service;
709  
710 shareToToolStripMenuItem.DropDownItems.Add(discoveredServiceMenuItem);
711 }
712  
713 private async void OnShareWithNodeClicked(object sender, EventArgs e)
714 {
715 var toolStripMenuItem = (ToolStripMenuItem)sender;
716  
717 var service = (Service)toolStripMenuItem.Tag;
718  
719 var rows = GetSelectedDataGridViewRows(dataGridView1);
720  
721 var count = rows.Count;
722  
723 toolStripProgressBar1.Minimum = 0;
724 toolStripProgressBar1.Maximum = count;
725  
726 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
727 {
728 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
729 {
730 Log.Error(rowProgressFailure.Exception, "Unable to transfer data.");
731  
732 toolStripStatusLabel1.Text =
733 $"Could not transfer {rowProgress.Row.Cells["NameColumn"].Value}...";
734 toolStripProgressBar1.Value = rowProgress.Index + 1;
735  
736 statusStrip1.Update();
737  
738 return;
739 }
740  
741 toolStripStatusLabel1.Text =
742 $"Transferred {rowProgress.Row.Cells["NameColumn"].Value}...";
743 toolStripProgressBar1.Value = rowProgress.Index + 1;
744  
745 statusStrip1.Update();
746 });
747  
748 await Task.Run(() => TransferFiles(rows, service, progress, _cancellationToken), _cancellationToken);
749  
750 if (_cancellationToken.IsCancellationRequested)
751 {
752 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
753 toolStripStatusLabel1.Text = "Done.";
754 }
755 }
756  
1 office 757 private void SnapshotManagerForm_Closing(object sender, FormClosingEventArgs e)
758 {
759 _cancellationTokenSource.Cancel();
760  
761 if (_snapshotPreviewForm != null)
762 {
763 _snapshotPreviewForm.Close();
764 _snapshotPreviewForm = null;
765 }
766  
767 if (_hexViewForm != null)
768 {
769 _hexViewForm.Close();
770 _hexViewForm = null;
771 }
772 }
773  
774 private void DataGridView1_DragEnter(object sender, DragEventArgs e)
775 {
776 if (e.Data.GetDataPresent(DataFormats.FileDrop))
777 {
778 e.Effect = DragDropEffects.Copy;
779 return;
780 }
781  
782 e.Effect = DragDropEffects.None;
783 }
784  
7 office 785 private void CreateSnapshots(IReadOnlyList<string> files, Bitmap screenCapture, TrackedFolders.TrackedFolders trackedFolders, IProgress<CreateSnapshotProgress> progress, CancellationToken cancellationToken)
5 office 786 {
787 Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = 512 }, async file =>
788 {
789 var color = Color.Empty;
790 if (_mainForm.TrackedFolders.TryGet(file, out var folder))
791 {
792 color = folder.Color;
793 }
794  
795 var fileInfo = File.GetAttributes(file);
796 if (fileInfo.HasFlag(FileAttributes.Directory))
797 {
798 foreach (var directoryFile in Directory.GetFiles(file, "*.*", SearchOption.AllDirectories))
799 {
800 var name = Path.GetFileName(directoryFile);
801 var path = Path.Combine(Path.GetDirectoryName(directoryFile), name);
802  
803 try
804 {
11 office 805 await _snapshotDatabase.CreateSnapshotAsync(name, path, screenCapture, color,
5 office 806 _cancellationToken);
807  
808 progress.Report(new CreateSnapshotProgressSuccess(file));
809 }
810 catch (Exception exception)
811 {
812 progress.Report(new CreateSnapshotProgressFailure(file, exception));
813 }
814 }
815  
816 return;
817 }
818  
819 var fileName = Path.GetFileName(file);
820 var pathName = Path.Combine(Path.GetDirectoryName(file), fileName);
821  
822 try
823 {
11 office 824 await _snapshotDatabase.CreateSnapshotAsync(fileName, pathName, screenCapture, color,
5 office 825 _cancellationToken);
826  
827 progress.Report(new CreateSnapshotProgressSuccess(file));
828 }
829 catch (Exception exception)
830 {
831 progress.Report(new CreateSnapshotProgressFailure(file, exception));
832 }
833 });
834 }
1 office 835 private async void DataGridView1_DragDrop(object sender, DragEventArgs e)
836 {
837 if (!e.Data.GetDataPresent(DataFormats.FileDrop))
838 {
839 return;
840 }
841  
842 var files = (string[])e.Data.GetData(DataFormats.FileDrop);
843  
844 toolStripProgressBar1.Minimum = 0;
845 toolStripProgressBar1.Maximum = files.Length;
846 toolStripStatusLabel1.Text = "Snapshotting files...";
847  
848 var screenCapture = ScreenCapture.Capture((CaptureMode)_mainForm.Configuration.CaptureMode);
5 office 849  
850 var progress = new Progress<CreateSnapshotProgress>(createSnapshotProgress =>
1 office 851 {
5 office 852 switch (createSnapshotProgress)
853 {
854 case CreateSnapshotProgressSuccess createSnapshotProgressSuccess:
855 toolStripStatusLabel1.Text = $"Snapshot taken of {createSnapshotProgressSuccess.File}.";
856 break;
857 case CreateSnapshotProgressFailure createSnapshotProgressFailure:
858 if (createSnapshotProgressFailure.Exception is SQLiteException { ResultCode: SQLiteErrorCode.Constraint })
859 {
860 toolStripStatusLabel1.Text = $"Snapshot of file {createSnapshotProgressFailure.File} already exists.";
861 break;
862 }
863  
864 toolStripStatusLabel1.Text = $"Could not snapshot file {createSnapshotProgressFailure.File}";
865 Log.Warning(createSnapshotProgressFailure.Exception, $"Could not snapshot file {createSnapshotProgressFailure.File}");
866 break;
867 }
868  
869 toolStripProgressBar1.Increment(1);
870 statusStrip1.Update();
871 });
872  
11 office 873 await Task.Factory.StartNew( () =>
5 office 874 {
7 office 875 CreateSnapshots(files, screenCapture, _mainForm.TrackedFolders, progress, _cancellationToken);
5 office 876 }, _cancellationToken);
1 office 877 }
878  
879 private async void FileToolStripMenuItem_Click(object sender, EventArgs e)
880 {
881 var dialog = new CommonOpenFileDialog();
882 if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
883 {
884 var screenCapture = ScreenCapture.Capture((CaptureMode)_mainForm.Configuration.CaptureMode);
885  
886 var fileName = Path.GetFileName(dialog.FileName);
887 var directory = Path.GetDirectoryName(dialog.FileName);
888 var pathName = Path.Combine(directory, fileName);
889  
890 var color = Color.Empty;
891 if (_mainForm.TrackedFolders.TryGet(directory, out var folder))
892 {
893 color = folder.Color;
894 }
895  
896 try
897 {
11 office 898 await _snapshotDatabase.CreateSnapshotAsync(fileName, pathName, screenCapture, color,
1 office 899 _cancellationToken);
900 }
901 catch (SQLiteException exception)
902 {
903 if (exception.ResultCode == SQLiteErrorCode.Constraint)
904 {
905 Log.Information(exception, "Snapshot already exists.");
906 }
907 }
908 catch (Exception exception)
909 {
910 Log.Warning(exception, "Could not create snapshot.");
911 }
912 }
913 }
914  
915 private async void DirectoryToolStripMenuItem_Click(object sender, EventArgs e)
916 {
917 var dialog = new CommonOpenFileDialog { IsFolderPicker = true };
918 if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
919 {
920 var screenCapture = ScreenCapture.Capture((CaptureMode)_mainForm.Configuration.CaptureMode);
921 foreach (var directoryFile in Directory.GetFiles(dialog.FileName, "*.*", SearchOption.AllDirectories))
922 {
923 var name = Path.GetFileName(directoryFile);
924 var directory = Path.GetDirectoryName(directoryFile);
925 var path = Path.Combine(directory, name);
926  
927 var color = Color.Empty;
928 if (_mainForm.TrackedFolders.TryGet(directory, out var folder))
929 {
930 color = folder.Color;
931 }
932  
933 try
934 {
11 office 935 await _snapshotDatabase.CreateSnapshotAsync(name, path, screenCapture, color, _cancellationToken);
1 office 936 }
937 catch (SQLiteException exception)
938 {
939 if (exception.ResultCode == SQLiteErrorCode.Constraint)
940 {
941 Log.Information(exception, "Snapshot already exists.");
942 }
943 }
944 catch (Exception exception)
945 {
946 Log.Warning(exception, "Could not create snapshot.");
947 }
948 }
949 }
950 }
951  
952 private async void RelocateToolStripMenuItem_Click(object sender, EventArgs e)
953 {
954 var commonOpenFileDialog = new CommonOpenFileDialog
955 {
956 InitialDirectory = _mainForm.Configuration.LastFolder,
957 IsFolderPicker = true
958 };
959  
960 if (commonOpenFileDialog.ShowDialog() != CommonFileDialogResult.Ok)
961 {
962 return;
963 }
964  
965 _mainForm.Configuration.LastFolder = commonOpenFileDialog.FileName;
966 _mainForm.ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
967 async () => await _mainForm.SaveConfiguration(), _cancellationToken);
968  
969 var directory = commonOpenFileDialog.FileName;
970  
971 var rows = GetSelectedDataGridViewRows(dataGridView1);
972  
973 var count = rows.Count;
974  
975 toolStripProgressBar1.Minimum = 0;
976 toolStripProgressBar1.Maximum = count;
977  
978 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
979 {
980 var path = Path.Combine(directory,
981 (string)rowProgress.Row.Cells["NameColumn"].Value);
982  
983 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
984 {
985 Log.Error(rowProgressFailure.Exception, "Could not relocate snapshot.");
986  
987 toolStripStatusLabel1.Text =
988 $"Could not relocate {rowProgress.Row.Cells["NameColumn"].Value} to {path}...";
989 toolStripProgressBar1.Value = rowProgress.Index + 1;
990  
991 statusStrip1.Update();
992  
993 return;
994 }
995  
996 rowProgress.Row.Cells["PathColumn"].Value = path;
997  
998 toolStripStatusLabel1.Text =
999 $"Relocated {rowProgress.Row.Cells["NameColumn"].Value} to {path}...";
1000 toolStripProgressBar1.Value = rowProgress.Index + 1;
1001  
1002 statusStrip1.Update();
1003 });
1004  
1005 await Task.Run(() => RelocateFiles(rows, directory, progress, _cancellationToken), _cancellationToken);
1006  
3 office 1007 if (_cancellationToken.IsCancellationRequested)
1 office 1008 {
1009 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
1010 toolStripStatusLabel1.Text = "Done.";
1011 }
1012 }
1013  
1014 private async void NoteToolStripMenuItem_Click(object sender, EventArgs e)
1015 {
1016 if (_snapshotNote != null)
1017 {
1018 return;
1019 }
1020  
1021 var row = GetSelectedDataGridViewRows(dataGridView1).FirstOrDefault();
1022 if (row == null)
1023 {
1024 return;
1025 }
1026  
1027 try
1028 {
11 office 1029 var snapshotPreview = await _snapshotDatabase.RetrievePreviewAsync(
1 office 1030 (string)row.Cells["HashColumn"].Value, _cancellationToken);
1031  
1032 if (snapshotPreview == null)
1033 {
1034 return;
1035 }
1036  
1037 _snapshotNote = new SnapshotNoteForm(this, snapshotPreview);
1038 _snapshotNote.Owner = this;
1039 _snapshotNote.SaveNote += SnapshotNote_SaveNote;
1040 _snapshotNote.Closing += SnapshotNote_Closing;
1041 _snapshotNote.Show();
1042 }
1043 catch (Exception exception)
1044 {
1045 Log.Error(exception, "Could not open notes form.");
1046 }
1047 }
1048  
1049 private async void SnapshotNote_SaveNote(object sender, SaveNoteEventArgs e)
1050 {
1051 var rows = GetSelectedDataGridViewRows(dataGridView1);
1052  
1053 var count = rows.Count;
1054  
1055 toolStripProgressBar1.Minimum = 0;
1056 toolStripProgressBar1.Maximum = count;
1057  
1058 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
1059 {
1060 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
1061 {
1062 Log.Error(rowProgressFailure.Exception, "Could not update note for snapshot.");
1063  
1064 toolStripStatusLabel1.Text =
1065 $"Could not update note for {rowProgress.Row.Cells["NameColumn"].Value}...";
1066 toolStripProgressBar1.Value = rowProgress.Index + 1;
1067  
1068 statusStrip1.Update();
1069  
1070 return;
1071 }
1072  
1073 toolStripStatusLabel1.Text =
1074 $"Updated note for {rowProgress.Row.Cells["NameColumn"].Value}...";
1075 toolStripProgressBar1.Value = rowProgress.Index + 1;
1076  
1077 statusStrip1.Update();
1078 });
1079  
1080 await Task.Run(() => UpdateNote(rows, e.Note, progress, _cancellationToken), _cancellationToken);
1081  
3 office 1082 if (_cancellationToken.IsCancellationRequested)
1 office 1083 {
1084 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
1085 toolStripStatusLabel1.Text = "Done.";
1086 }
1087 }
1088  
1089 private void SnapshotNote_Closing(object sender, CancelEventArgs e)
1090 {
1091 if (_snapshotNote == null)
1092 {
1093 return;
1094 }
1095  
1096 _snapshotNote.Closing -= SnapshotNote_Closing;
1097 _snapshotNote.Close();
1098 _snapshotNote = null;
1099 }
1100  
1101 private async void ViewHexToolStripMenuItem_Click(object sender, EventArgs e)
1102 {
1103 var rows = GetSelectedDataGridViewRows(dataGridView1);
1104 var row = rows.FirstOrDefault();
1105 if (row == null)
1106 {
1107 return;
1108 }
1109  
1110 var hash = (string)row.Cells["HashColumn"].Value;
1111  
11 office 1112 using (var memoryStream = await _snapshotDatabase.RetrieveFileStreamAsync(hash, _cancellationToken))
1 office 1113 {
1114 if (memoryStream == null)
1115 {
1116 return;
1117 }
1118  
1119 if (_hexViewForm != null)
1120 {
1121 _hexViewForm.UpdateData(memoryStream.ToArray());
1122 _hexViewForm.Activate();
1123 return;
1124 }
1125  
1126 _hexViewForm = new HexViewForm(_snapshotDatabase, hash, memoryStream.ToArray());
1127 _hexViewForm.Owner = this;
1128 _hexViewForm.Closing += HexViewForm_Closing;
1129 _hexViewForm.SaveData += HexViewForm_SaveData;
1130  
1131 _hexViewForm.Show();
1132 }
1133 }
1134  
1135 private async void HexViewForm_SaveData(object sender, SaveDataEventArgs e)
1136 {
11 office 1137 var hash = await _snapshotDatabase.UpdateFileAsync(e.Hash, e.Data, _cancellationToken);
1 office 1138  
1139 if (string.IsNullOrEmpty(hash))
1140 {
1141 return;
1142 }
1143  
1144 dataGridView1.InvokeIfRequired(dataGridView =>
1145 {
1146 // Update the hash in the datagridview.
1147 var removeRows = new List<DataGridViewRow>();
1148 foreach (var row in dataGridView.Rows.OfType<DataGridViewRow>())
1149 {
1150 if ((string)row.Cells["HashColumn"].Value == hash)
1151 {
1152 removeRows.Add(row);
1153 }
1154  
1155 if ((string)row.Cells["HashColumn"].Value != e.Hash)
1156 {
1157 continue;
1158 }
1159  
1160 row.Cells["HashColumn"].Value = hash;
1161 }
1162  
1163 // Remove rows that might have the same hash.
1164 foreach (var row in removeRows)
1165 {
1166 dataGridView.Rows.Remove(row);
1167 }
1168 });
1169 }
1170  
1171 private void HexViewForm_Closing(object sender, CancelEventArgs e)
1172 {
1173 if (_hexViewForm == null)
1174 {
1175 return;
1176 }
1177  
1178 _hexViewForm.SaveData -= HexViewForm_SaveData;
1179 _hexViewForm.Closing -= HexViewForm_Closing;
1180 _hexViewForm.Close();
1181 _hexViewForm = null;
1182 }
1183  
1184 private async void FileToolStripMenuItem2_Click(object sender, EventArgs e)
1185 {
1186 var commonOpenFileDialog = new CommonOpenFileDialog
1187 {
1188 InitialDirectory = _mainForm.Configuration.LastFolder,
1189 IsFolderPicker = true
1190 };
1191  
1192 if (commonOpenFileDialog.ShowDialog() == CommonFileDialogResult.Ok)
1193 {
1194 _mainForm.Configuration.LastFolder = commonOpenFileDialog.FileName;
1195 _mainForm.ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
1196 async () => await _mainForm.SaveConfiguration(), _cancellationToken);
1197  
1198 var directory = commonOpenFileDialog.FileName;
1199  
1200 var rows = GetSelectedDataGridViewRows(dataGridView1);
1201  
1202 var count = rows.Count;
1203  
1204 toolStripProgressBar1.Minimum = 0;
1205 toolStripProgressBar1.Maximum = count;
1206  
1207 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
1208 {
1209 var fileInfo =
1210 new FileInfo((string)rowProgress.Row.Cells["NameColumn"].Value);
1211 var file = fileInfo.Name;
1212 var path = Path.Combine(directory, file);
1213  
1214 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
1215 {
1216 Log.Error(rowProgressFailure.Exception, "Could not save snapshot.");
1217  
1218 toolStripStatusLabel1.Text =
1219 $"Could not save snapshot {rowProgress.Row.Cells["NameColumn"].Value} to {path}...";
1220 toolStripProgressBar1.Value = rowProgress.Index + 1;
1221  
1222 statusStrip1.Update();
1223  
1224 return;
1225 }
1226  
1227 toolStripStatusLabel1.Text =
1228 $"Saved {rowProgress.Row.Cells["NameColumn"].Value} to {path}...";
1229 toolStripProgressBar1.Value = rowProgress.Index + 1;
1230  
1231 statusStrip1.Update();
1232 });
1233  
1234 await Task.Run(() => SaveFilesTo(rows, directory, progress, _cancellationToken), _cancellationToken);
1235  
3 office 1236 if (_cancellationToken.IsCancellationRequested)
1 office 1237 {
1238 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
1239 toolStripStatusLabel1.Text = "Done.";
1240 }
1241 }
1242 }
1243  
1244 private async void DirectoryToolStripMenuItem2_Click(object sender, EventArgs e)
1245 {
1246 var select = GetSelectedDataGridViewRows(dataGridView1).FirstOrDefault();
1247  
1248 if (select == null)
1249 {
1250 return;
1251 }
1252  
1253 // C:\aa\bbb\dd.txt
1254 var path = (string)select.Cells["PathColumn"].Value;
1255  
1256 // C:\aa\bbb\
1257 var basePath = Path.GetDirectoryName(path);
1258  
1259 var dialog = new CommonOpenFileDialog { IsFolderPicker = true };
1260 if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
1261 {
1262 //Log.Information(dialog.FileName);
1263 var rows = GetAllDataGridViewRows(dataGridView1);
1264 var count = rows.Count;
1265  
1266 toolStripProgressBar1.Minimum = 0;
1267 toolStripProgressBar1.Maximum = count;
1268 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
1269 {
1270 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
1271 {
1272 Log.Error(rowProgressFailure.Exception, "Could not save file.");
1273  
1274 toolStripStatusLabel1.Text =
1275 $"Could not save file {rowProgress.Row.Cells["NameColumn"].Value} to {basePath}...";
1276 toolStripProgressBar1.Value = rowProgress.Index + 1;
1277  
1278 statusStrip1.Update();
1279  
1280 return;
1281 }
1282  
1283 toolStripStatusLabel1.Text =
1284 $"Saved {rowProgress.Row.Cells["NameColumn"].Value} to {basePath}...";
1285 toolStripProgressBar1.Value = rowProgress.Index + 1;
1286  
1287 statusStrip1.Update();
1288 });
1289  
1290 await Task.Run(() => SaveDirectoryTo(rows, basePath, dialog.FileName, progress, _cancellationToken),
1291 _cancellationToken);
1292  
3 office 1293 if (_cancellationToken.IsCancellationRequested)
1 office 1294 {
1295 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
1296 toolStripStatusLabel1.Text = "Done.";
1297 }
1298 }
1299 }
1300  
1301 private void TextBox1_TextChanged(object sender, EventArgs e)
1302 {
1303 _searchTextBoxChangedContinuation.Schedule(TimeSpan.FromSeconds(1), () =>
1304 {
1305 textBox1.InvokeIfRequired(textBox =>
1306 {
1307 var search = textBox.Text;
1308  
1309 dataGridView1.InvokeIfRequired(dataGridView =>
1310 {
1311 foreach (var row in GetAllDataGridViewRows(dataGridView))
1312 {
1313 if(row.Cells["PathColumn"].Value == null)
1314 {
1315 continue;
1316 }
1317  
1318 switch (((string)row.Cells["PathColumn"].Value).IndexOf(search,
1319 StringComparison.OrdinalIgnoreCase))
1320 {
1321 case -1:
1322 row.Visible = false;
1323 break;
1324 default:
1325 row.Visible = true;
1326 break;
1327 }
1328 }
1329 });
1330 });
1331 }, _cancellationToken);
1332 }
1333  
1334 private async void RecomputeHashesToolStripMenuItem1_Click(object sender, EventArgs e)
1335 {
1336 var rows = GetSelectedDataGridViewRows(dataGridView1);
1337  
1338 var count = rows.Count;
1339  
1340 toolStripProgressBar1.Minimum = 0;
1341 toolStripProgressBar1.Maximum = count;
1342  
1343 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
1344 {
1345 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
1346 {
1347 Log.Error(rowProgressFailure.Exception, "Could not recompute hash for snapshot.");
1348  
1349 toolStripStatusLabel1.Text =
1350 $"Could not recompute hash for {rowProgress.Row.Cells["NameColumn"].Value}...";
1351 toolStripProgressBar1.Value = rowProgress.Index + 1;
1352  
1353 statusStrip1.Update();
1354  
1355 return;
1356 }
1357  
1358 toolStripStatusLabel1.Text =
1359 $"Recomputed hash for {rowProgress.Row.Cells["NameColumn"].Value}...";
1360 toolStripProgressBar1.Value = rowProgress.Index + 1;
1361  
1362 statusStrip1.Update();
1363 });
1364  
1365 await Task.Run(() => RecomputeHashes(rows, progress, _cancellationToken), _cancellationToken);
1366  
3 office 1367 if (_cancellationToken.IsCancellationRequested)
1 office 1368 {
1369 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
1370 toolStripStatusLabel1.Text = "Done.";
1371 }
1372 }
1373  
1374 private async void NormalizeDateTimeToolStripMenuItem_Click(object sender, EventArgs e)
1375 {
1376 var rows = GetSelectedDataGridViewRows(dataGridView1);
1377  
1378 var count = rows.Count;
1379  
1380 toolStripProgressBar1.Minimum = 0;
1381 toolStripProgressBar1.Maximum = count;
1382  
1383 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
1384 {
1385 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
1386 {
1387 Log.Error(rowProgressFailure.Exception, "Could not normalize date-time for snapshot.");
1388  
1389 toolStripStatusLabel1.Text =
1390 $"Could not normalize date-time for {rowProgress.Row.Cells["NameColumn"].Value}...";
1391 toolStripProgressBar1.Value = rowProgress.Index + 1;
1392  
1393 statusStrip1.Update();
1394  
1395 return;
1396 }
1397  
1398 toolStripStatusLabel1.Text =
1399 $"Normalized date-time for {rowProgress.Row.Cells["NameColumn"].Value}...";
1400 toolStripProgressBar1.Value = rowProgress.Index + 1;
1401  
1402 statusStrip1.Update();
1403 });
1404  
1405 await Task.Run(() => NormalizeDateTime(rows, progress, _cancellationToken), _cancellationToken);
1406  
3 office 1407 if (_cancellationToken.IsCancellationRequested)
1 office 1408 {
1409 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
1410 toolStripStatusLabel1.Text = "Done.";
1411 }
1412 }
1413  
1414 #endregion
1415  
1416 #region Private Methods
12 office 1417 private void DataGridView1_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
1418 {
1419 DataGridView dataGridView = sender as DataGridView;
1420 foreach (DataGridViewCell cell in dataGridView.Rows[e.RowIndex].Cells)
1421 {
1422 if (cell.Selected == false) { continue; }
1423 var bgColorCell = Color.White;
1424 if (cell.Style.BackColor != Color.Empty) { bgColorCell = cell.Style.BackColor; }
1425 else if (cell.InheritedStyle.BackColor != Color.Empty) { bgColorCell = cell.InheritedStyle.BackColor; }
1426 cell.Style.SelectionBackColor = MixColor(bgColorCell, Color.FromArgb(0, 150, 255), 10, 4);
1427 }
1428 }
1 office 1429  
12 office 1430 //Mix two colors
1431 //Example: Steps=10 & Position=4 makes Color2 mix 40% into Color1
1432 /// <summary>
1433 /// Mix two colors.
1434 /// </summary>
1435 /// <param name="Color1"></param>
1436 /// <param name="Color2"></param>
1437 /// <param name="Steps"></param>
1438 /// <param name="Position"></param>
1439 /// <example>Steps=10 & Positon=4 makes Color2 mix 40% into Color1</example>
1440 /// <remarks>https://stackoverflow.com/questions/38337849/transparent-selectionbackcolor-for-datagridview-cell</remarks>
1441 /// <returns></returns>
1442 public static Color MixColor(Color Color1, Color Color2, int Steps, int Position)
1443 {
1444 if (Position <= 0 || Steps <= 1) { return Color1; }
1445 if (Position >= Steps) { return Color2; }
1446 return Color.FromArgb(
1447 Color1.R + ((Color2.R - Color1.R) / Steps * Position),
1448 Color1.G + ((Color2.G - Color1.G) / Steps * Position),
1449 Color1.B + ((Color2.B - Color1.B) / Steps * Position)
1450 );
1451 }
1452  
1 office 1453 private async Task DeleteFiles(IReadOnlyList<DataGridViewRow> rows, IProgress<DataGridViewRowProgress> progress,
1454 CancellationToken cancellationToken)
1455 {
1456 var count = rows.Count;
1457  
1458 for (var index = 0; index < count && !cancellationToken.IsCancellationRequested; ++index)
1459 {
1460 try
1461 {
11 office 1462 await _snapshotDatabase.RemoveFileAsync((string)rows[index].Cells["HashColumn"].Value,
1 office 1463 cancellationToken);
1464  
1465 progress.Report(new DataGridViewRowProgressSuccess(rows[index], index));
1466 }
1467 catch (Exception exception)
1468 {
1469 progress.Report(new DataGridViewRowProgressFailure(rows[index], index, exception));
1470 }
1471 }
1472 }
1473  
1474 private async Task DeleteFilesFast(IReadOnlyList<DataGridViewRow> rows, CancellationToken cancellationToken)
1475 {
1476 var hashes = rows.Select(row => (string)row.Cells["HashColumn"].Value);
1477  
11 office 1478 await _snapshotDatabase.RemoveFileFastAsync(hashes, cancellationToken);
1 office 1479 }
1480  
1481 private async Task UpdateNote(IReadOnlyList<DataGridViewRow> rows, string note,
1482 IProgress<DataGridViewRowProgress> progress, CancellationToken cancellationToken)
1483 {
1484 var count = rows.Count;
1485  
1486 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1487 {
1488 try
1489 {
11 office 1490 await _snapshotDatabase.UpdateNoteAsync((string)rows[i].Cells["HashColumn"].Value, note,
1 office 1491 cancellationToken);
1492  
1493 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1494 }
1495 catch (Exception exception)
1496 {
1497 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1498 }
1499 }
1500 }
1501  
1502 private static List<DataGridViewRow> GetSelectedDataGridViewRows(DataGridView dataGridView)
1503 {
1504 return dataGridView.SelectedRows.OfType<DataGridViewRow>().ToList();
1505 }
1506  
1507 private static List<DataGridViewRow> GetAllDataGridViewRows(DataGridView dataGridView)
1508 {
1509 return dataGridView.Rows.OfType<DataGridViewRow>().ToList();
1510 }
1511  
1512 private async Task RemoveColorFiles(IReadOnlyList<DataGridViewRow> rows,
1513 IProgress<DataGridViewRowProgress> progress,
1514 CancellationToken cancellationToken)
1515 {
1516 var count = rows.Count;
1517  
1518 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1519 {
1520 try
1521 {
11 office 1522 await _snapshotDatabase.RemoveColorAsync((string)rows[i].Cells["HashColumn"].Value,
1 office 1523 cancellationToken);
1524  
1525 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1526 }
1527 catch (Exception exception)
1528 {
1529 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1530 }
1531 }
1532 }
1533  
1534 private async Task ColorFiles(IReadOnlyList<DataGridViewRow> rows, Color color,
1535 IProgress<DataGridViewRowProgress> progress, CancellationToken cancellationToken)
1536 {
1537 var count = rows.Count;
1538  
1539 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1540 {
1541 try
1542 {
11 office 1543 await _snapshotDatabase.UpdateColorAsync((string)rows[i].Cells["HashColumn"].Value, color,
1 office 1544 cancellationToken);
1545  
1546 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1547 }
1548 catch (Exception exception)
1549 {
1550 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1551 }
1552 }
1553 }
1554  
12 office 1555  
1556 private async Task TransferFiles(IReadOnlyList<DataGridViewRow> rows, Service service,
1557 IProgress<DataGridViewRowProgress> progress, CancellationToken cancellationToken)
1558 {
1559  
1560 var client = new WatsonTcpClient($"{service.HostEntry.AddressList[0]}", service.UPort);
1561 client.Events.MessageReceived += (sender, args) =>
1562 {
1563 Log.Information($"{args.Data.Length} byte long message received from {args.Client.IpPort}");
1564 };
1565  
1566 try
1567 {
1568 client.Connect();
1569  
1570 var count = rows.Count;
1571  
1572 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1573 {
1574 try
1575 {
20 office 1576 var completeSnapshot = await _snapshotDatabase.GenerateTransferSnapshotAsync((string)rows[i].Cells["HashColumn"].Value, cancellationToken);
12 office 1577  
1578 var jsonSnapshot = JsonConvert.SerializeObject(completeSnapshot);
1579  
1580 await client.SendAsync(jsonSnapshot, null, cancellationToken);
1581  
1582 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1583 }
1584 catch (Exception exception)
1585 {
1586 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1587 }
1588 }
1589 }
1590 finally
1591 {
1592 client.Dispose();
1593 }
1594  
1595 }
1596  
1 office 1597 private async Task RevertFile(IReadOnlyList<DataGridViewRow> rows, IProgress<DataGridViewRowProgress> progress,
1598 CancellationToken cancellationToken)
1599 {
1600 var count = rows.Count;
1601  
1602 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1603 {
1604 try
1605 {
11 office 1606 await _snapshotDatabase.RevertFileAsync((string)rows[i].Cells["NameColumn"].Value,
1 office 1607 (string)rows[i].Cells["HashColumn"].Value,
1608 cancellationToken, _mainForm.Configuration.AtomicOperations);
1609  
1610 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1611 }
1612 catch (Exception exception)
1613 {
1614 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1615 }
1616 }
1617 }
1618  
1619 private async void SaveFilesTo(IReadOnlyList<DataGridViewRow> rows, string directory,
1620 IProgress<DataGridViewRowProgress> progress, CancellationToken cancellationToken)
1621 {
1622 var count = rows.Count;
1623  
1624 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1625 {
1626 try
1627 {
1628 var fileInfo = new FileInfo((string)rows[i].Cells["NameColumn"].Value);
1629 var file = fileInfo.Name;
1630 var path = Path.Combine(directory, file);
1631  
11 office 1632 await _snapshotDatabase.SaveFileAsync(path, (string)rows[i].Cells["HashColumn"].Value,
1 office 1633 cancellationToken);
1634  
1635 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1636 }
1637 catch (Exception exception)
1638 {
1639 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1640 }
1641 }
1642 }
1643  
1644 private async Task RelocateFiles(IReadOnlyList<DataGridViewRow> rows, string directory,
1645 IProgress<DataGridViewRowProgress> progress,
1646 CancellationToken cancellationToken)
1647 {
1648 var count = rows.Count;
1649  
1650 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1651 {
1652 try
1653 {
1654 var path = Path.Combine(directory, (string)rows[i].Cells["NameColumn"].Value);
1655  
11 office 1656 await _snapshotDatabase.RelocateFileAsync((string)rows[i].Cells["HashColumn"].Value, path,
1 office 1657 cancellationToken);
1658  
1659 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1660 }
1661 catch (Exception exception)
1662 {
1663 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1664 }
1665 }
1666 }
1667  
1668 private async void RecomputeHashes(IReadOnlyList<DataGridViewRow> rows,
1669 IProgress<DataGridViewRowProgress> progress,
1670 CancellationToken cancellationToken)
1671 {
1672 var count = rows.Count;
1673  
1674 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1675 {
1676 try
1677 {
1678 using (var memoryStream =
11 office 1679 await _snapshotDatabase.RetrieveFileStreamAsync((string)rows[i].Cells["HashColumn"].Value,
1 office 1680 cancellationToken))
1681 {
1682 if (memoryStream == null)
1683 {
1684 continue;
1685 }
1686  
1687 using (var md5 = MD5.Create())
1688 {
1689 var recomputedHash = md5.ComputeHash(memoryStream);
1690 var hashHex = BitConverter.ToString(recomputedHash).Replace("-", "")
1691 .ToLowerInvariant();
1692  
11 office 1693 await _snapshotDatabase.UpdateHashAsync((string)rows[i].Cells["HashColumn"].Value, hashHex,
1 office 1694 cancellationToken);
1695  
1696 rows[i].Cells["HashColumn"].Value = hashHex;
1697  
1698 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1699 }
1700 }
1701 }
1702 catch (Exception exception)
1703 {
1704 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1705 }
1706 }
1707 }
1708  
1709 private async Task SaveDirectoryTo(IReadOnlyList<DataGridViewRow> rows, string basePath, string targetPath,
1710 IProgress<DataGridViewRowProgress> progress,
1711 CancellationToken cancellationToken)
1712 {
1713 var store = new HashSet<string>();
1714  
1715 var count = rows.Count;
1716  
1717 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1718 {
1719 try
1720 {
1721 // C:\aa\bbb\fff\gg.txt
1722 var rowPath = (string)rows[i].Cells["PathColumn"].Value;
1723 if (store.Contains(rowPath))
1724 {
1725 continue;
1726 }
1727  
1728 // C:\aa\bbb\fff\gg.txt subpath C:\aa\bbb\
1729 if (!rowPath.IsPathEqual(basePath) &&
1730 !rowPath.IsSubPathOf(basePath))
1731 {
1732 continue;
1733 }
1734  
1735 var rootPath = new DirectoryInfo(basePath).Name;
1736 var relPath = rowPath.Remove(0, basePath.Length).Trim('\\');
1737 var newPath = Path.Combine(targetPath, rootPath, relPath);
1738  
1739 var hash = (string)rows[i].Cells["HashColumn"].Value;
11 office 1740 await _snapshotDatabase.SaveFileAsync(newPath, hash, cancellationToken);
1 office 1741  
1742 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1743  
1744 if (!store.Contains(rowPath))
1745 {
1746 store.Add(rowPath);
1747 }
1748 }
1749 catch (Exception exception)
1750 {
1751 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1752 }
1753 }
1754 }
1755  
1756 private async Task NormalizeDateTime(IReadOnlyList<DataGridViewRow> rows,
1757 IProgress<DataGridViewRowProgress> progress,
1758 CancellationToken cancellationToken)
1759 {
1760 var count = rows.Count;
1761  
1762 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1763 {
1764 try
1765 {
11 office 1766 await _snapshotDatabase.NormalizeTimeAsync((string)rows[i].Cells["HashColumn"].Value,
1 office 1767 cancellationToken);
1768  
1769 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1770 }
1771 catch (Exception exception)
1772 {
1773 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1774 }
1775 }
1776 }
1777  
1778 private async void deleteToolStripMenuItem1_Click(object sender, EventArgs e)
1779 {
1780 var toolStripMenuItem = (ToolStripMenuItem)sender;
1781  
1782 var rows = GetSelectedDataGridViewRows(dataGridView1);
1783  
1784 var count = rows.Count;
1785  
1786 toolStripProgressBar1.Minimum = 0;
1787 toolStripProgressBar1.Maximum = count;
1788  
1789 var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
1790 {
1791 if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
1792 {
1793 Log.Error(rowProgressFailure.Exception, "Unable to delete screenshot.");
1794  
1795 toolStripStatusLabel1.Text =
1796 $"Could not delete screenshot for {rowProgress.Row.Cells["NameColumn"].Value}...";
1797 toolStripProgressBar1.Value = rowProgress.Index + 1;
1798  
1799 statusStrip1.Update();
1800  
1801 return;
1802 }
1803  
1804 toolStripStatusLabel1.Text =
1805 $"Colored {rowProgress.Row.Cells["NameColumn"].Value}...";
1806 toolStripProgressBar1.Value = rowProgress.Index + 1;
1807  
1808 statusStrip1.Update();
1809 });
1810  
1811 await Task.Run(() => DeleteScreenshots(rows, progress, _cancellationToken), _cancellationToken);
1812  
3 office 1813 if (_cancellationToken.IsCancellationRequested)
1 office 1814 {
1815 toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
1816 toolStripStatusLabel1.Text = "Done.";
1817 }
1818 }
1819  
1820 private async Task DeleteScreenshots(IReadOnlyList<DataGridViewRow> rows,
1821 IProgress<DataGridViewRowProgress> progress,
1822 CancellationToken cancellationToken)
1823 {
1824 var count = rows.Count;
1825  
1826 for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
1827 {
1828 try
1829 {
11 office 1830 await _snapshotDatabase.DeleteScreenshotAsync((string)rows[i].Cells["HashColumn"].Value,
1 office 1831 cancellationToken);
1832  
1833 progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
1834 }
1835 catch (Exception exception)
1836 {
1837 progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
1838 }
1839 }
1840 }
1841  
1842 #endregion
1843 }
1844 }