Horizon – Diff between revs 12 and 13

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