Horizon – Diff between revs 23 and 24

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