Horizon – Blame information for rev 7

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