Horizon – Diff between revs 14 and 20

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