Horizon – Blame information for rev 5

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