Horizon – Blame information for rev 37

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