Horizon – Blame information for rev 11

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