Horizon – Diff between revs 5 and 6

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