Horizon – Rev 3
?pathlinks?
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.SQLite;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Horizon.Database;
using Horizon.Utilities;
using Microsoft.WindowsAPICodePack.Dialogs;
using Serilog;
namespace Horizon.Snapshots
{
public partial class SnapshotManagerForm : Form
{
#region Static Fields and Constants
private static ScheduledContinuation _searchTextBoxChangedContinuation;
#endregion
#region Public Events & Delegates
public event EventHandler<PreviewRetrievedEventArgs> PreviewRetrieved;
#endregion
#region Private Delegates, Events, Enums, Properties, Indexers and Fields
private readonly MainForm _mainForm;
private readonly SnapshotDatabase _snapshotDatabase;
private HexViewForm _hexViewForm;
private SnapshotNoteForm _snapshotNote;
private SnapshotPreviewForm _snapshotPreviewForm;
private readonly object _mouseMoveLock = new object();
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly CancellationToken _cancellationToken;
private readonly CancellationTokenSource _localCancellationTokenSource;
private readonly CancellationToken _localCancellationToken;
#endregion
#region Constructors, Destructors and Finalizers
public SnapshotManagerForm()
{
InitializeComponent();
Utilities.WindowState.FormTracker.Track(this);
dataGridView1.Columns["TimeColumn"].ValueType = typeof(DateTime);
_searchTextBoxChangedContinuation = new ScheduledContinuation();
_localCancellationTokenSource = new CancellationTokenSource();
_localCancellationToken = _localCancellationTokenSource.Token;
}
public SnapshotManagerForm(MainForm mainForm, SnapshotDatabase snapshotDatabase,
CancellationToken cancellationToken) : this()
{
_mainForm = mainForm;
_snapshotDatabase = snapshotDatabase;
_snapshotDatabase.SnapshotCreate += SnapshotManager_SnapshotCreate;
_cancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, cancellationToken);
_cancellationToken = _cancellationTokenSource.Token;
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && components != null)
{
components.Dispose();
}
_snapshotDatabase.SnapshotCreate -= SnapshotManager_SnapshotCreate;
_localCancellationTokenSource.Cancel();
base.Dispose(disposing);
}
#endregion
#region Event Handlers
private void DataGridView1_MouseDown(object sender, MouseEventArgs e)
{
var dataGridView = (DataGridView)sender;
var index = dataGridView.HitTest(e.X, e.Y).RowIndex;
if (index == -1)
{
base.OnMouseDown(e);
return;
}
if (!dataGridView.SelectedRows.Contains(dataGridView.Rows[index]))
{
base.OnMouseDown(e);
}
}
private async void DataGridView1_MouseMove(object sender, MouseEventArgs e)
{
var dataGridView = (DataGridView)sender;
// Only accept dragging with left mouse button.
switch (e.Button)
{
case MouseButtons.Left:
if (!Monitor.TryEnter(_mouseMoveLock))
{
break;
}
try
{
var index = dataGridView.HitTest(e.X, e.Y).RowIndex;
if (index == -1)
{
base.OnMouseMove(e);
return;
}
var rows = GetSelectedDataGridViewRows(dataGridView);
var count = rows.Count;
if (count == 0)
{
base.OnMouseMove(e);
break;
}
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = count;
var virtualFileDataObject = new VirtualFileDataObject.VirtualFileDataObject();
var fileDescriptors =
new List<VirtualFileDataObject.VirtualFileDataObject.FileDescriptor>(count);
var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
{
if (_cancellationToken.IsCancellationRequested)
{
return;
}
if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
{
Log.Error(rowProgressFailure.Exception, "Unable to retrieve data for row.");
toolStripStatusLabel1.Text =
$"Could not read file data {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
return;
}
if (rowProgress is DataGridViewRowProgressSuccessRetrieveFileStream
rowProgressSuccessRetrieveFileStream)
{
toolStripStatusLabel1.Text =
$"Got {rowProgress.Row.Cells["NameColumn"].Value} file stream...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
var hash = (string)rowProgressSuccessRetrieveFileStream.Row.Cells["HashColumn"].Value;
var name = (string)rowProgressSuccessRetrieveFileStream.Row.Cells["NameColumn"].Value;
var fileDescriptor = new VirtualFileDataObject.VirtualFileDataObject.FileDescriptor
{
Name = name,
StreamContents = stream =>
{
rowProgressSuccessRetrieveFileStream.MemoryStream.Seek(0, SeekOrigin.Begin);
rowProgressSuccessRetrieveFileStream.MemoryStream.CopyTo(stream);
}
};
fileDescriptors.Add(fileDescriptor);
}
});
await Task.Run(() => RetrieveFileStream(rows, progress, _cancellationToken), _cancellationToken);
if (_cancellationToken.IsCancellationRequested)
{
toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
toolStripStatusLabel1.Text = "Done.";
}
virtualFileDataObject.SetData(fileDescriptors);
dataGridView1.DoDragDrop(virtualFileDataObject, DragDropEffects.Copy);
}
finally
{
Monitor.Exit(_mouseMoveLock);
}
break;
}
}
private async Task RetrieveFileStream(IReadOnlyList<DataGridViewRow> rows,
IProgress<DataGridViewRowProgress> progress,
CancellationToken cancellationToken)
{
var count = rows.Count;
for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
{
try
{
var fileStream = await _snapshotDatabase.RetrieveFileStream(
(string)rows[i].Cells["HashColumn"].Value,
cancellationToken);
progress.Report(new DataGridViewRowProgressSuccessRetrieveFileStream(rows[i], i, fileStream));
}
catch (Exception exception)
{
progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
}
}
}
private void SnapshotManagerForm_Resize(object sender, EventArgs e)
{
if (_snapshotPreviewForm != null)
{
_snapshotPreviewForm.WindowState = WindowState;
}
}
private void OpenInExplorerToolStripMenuItem_Click(object sender, EventArgs e)
{
var row = GetSelectedDataGridViewRows(dataGridView1).FirstOrDefault();
if (row == null)
{
return;
}
Process.Start("explorer.exe", $"/select, \"{(string)row.Cells["PathColumn"].Value}\"");
}
private async void DataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
{
var dataGridView = (DataGridView)sender;
if (_snapshotPreviewForm == null)
{
_snapshotPreviewForm = new SnapshotPreviewForm(this, _snapshotDatabase);
_snapshotPreviewForm.Owner = this;
_snapshotPreviewForm.Closing += SnapshotPreviewForm_Closing;
_snapshotPreviewForm.Show();
}
var row = GetSelectedDataGridViewRows(dataGridView).FirstOrDefault();
if (row == null)
{
return;
}
var hash = (string)row.Cells["HashColumn"].Value;
var snapshotPreview =
await Task.Run(async () => await _snapshotDatabase.RetrievePreview(hash, _cancellationToken),
_cancellationToken);
if (snapshotPreview == null)
{
return;
}
PreviewRetrieved?.Invoke(this, new PreviewRetrievedEventArgs(snapshotPreview));
}
private void SnapshotPreviewForm_Closing(object sender, CancelEventArgs e)
{
if (_snapshotPreviewForm == null)
{
return;
}
_snapshotPreviewForm.Dispose();
_snapshotPreviewForm = null;
}
private async void NoneToolStripMenuItem_Click(object sender, EventArgs e)
{
var rows = GetSelectedDataGridViewRows(dataGridView1);
var count = rows.Count;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = count;
var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
{
if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
{
Log.Error(rowProgressFailure.Exception, "Failed to remove color from row.");
toolStripStatusLabel1.Text =
$"Could not remove color from {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
return;
}
rowProgress.Row.DefaultCellStyle.BackColor = Color.Empty;
toolStripStatusLabel1.Text =
$"Removed color from {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
});
await Task.Run(() => RemoveColorFiles(rows, progress, _cancellationToken), _cancellationToken);
if (_cancellationToken.IsCancellationRequested)
{
toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
toolStripStatusLabel1.Text = "Done.";
}
}
private async void ColorToolStripMenuItem_Click(object sender, EventArgs e)
{
var toolStripMenuItem = (ToolStripMenuItem)sender;
var color = toolStripMenuItem.BackColor;
var rows = GetSelectedDataGridViewRows(dataGridView1);
var count = rows.Count;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = count;
var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
{
if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
{
Log.Error(rowProgressFailure.Exception, "Unable to color row.");
toolStripStatusLabel1.Text =
$"Could not color {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
return;
}
rowProgress.Row.DefaultCellStyle.BackColor = color;
toolStripStatusLabel1.Text =
$"Colored {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
});
await Task.Run(() => ColorFiles(rows, color, progress, _cancellationToken), _cancellationToken);
if (_cancellationToken.IsCancellationRequested)
{
toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
toolStripStatusLabel1.Text = "Done.";
}
}
private async void DeleteToolStripMenuItem_Click(object sender, EventArgs e)
{
var rows = GetSelectedDataGridViewRows(dataGridView1);
var count = rows.Count;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = count;
var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
{
if (_cancellationToken.IsCancellationRequested)
{
return;
}
if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
{
Log.Error(rowProgressFailure.Exception, "Unable to delete row.");
toolStripStatusLabel1.Text =
$"Could not remove {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
return;
}
toolStripStatusLabel1.Text =
$"Removed {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
dataGridView1.Rows.Remove(rowProgress.Row);
});
await Task.Run(() => DeleteFiles(rows, progress, _cancellationToken), _cancellationToken);
if (_cancellationToken.IsCancellationRequested)
{
toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
toolStripStatusLabel1.Text = "Done.";
}
}
private async void DeleteFastToolStripMenuItem_Click(object sender, EventArgs e)
{
var rows = GetSelectedDataGridViewRows(dataGridView1);
try
{
await DeleteFilesFast(rows, _cancellationToken);
foreach (var row in rows)
{
dataGridView1.Rows.Remove(row);
}
}
catch (Exception exception)
{
Log.Error(exception, "Unable to remove rows.");
}
}
private void SnapshotManager_SnapshotCreate(object sender, SnapshotCreateEventArgs e)
{
switch (e)
{
case SnapshotCreateSuccessEventArgs snapshotCreateSuccessEventArgs:
dataGridView1.InvokeIfRequired(dataGridView =>
{
var index = dataGridView.Rows.Add();
dataGridView.Rows[index].Cells["TimeColumn"].Value =
DateTime.Parse(snapshotCreateSuccessEventArgs.Time);
dataGridView.Rows[index].Cells["NameColumn"].Value = snapshotCreateSuccessEventArgs.Name;
dataGridView.Rows[index].Cells["PathColumn"].Value = snapshotCreateSuccessEventArgs.Path;
dataGridView.Rows[index].Cells["HashColumn"].Value = snapshotCreateSuccessEventArgs.Hash;
dataGridView.Rows[index].DefaultCellStyle.BackColor = snapshotCreateSuccessEventArgs.Color;
dataGridView.Sort(dataGridView.Columns["TimeColumn"], ListSortDirection.Descending);
});
break;
case SnapshotCreateFailureEventArgs snapshotCreateFailure:
Log.Warning(snapshotCreateFailure.Exception, "Could not create snapshot.");
break;
}
}
private void RevertToThisToolStripMenuItem_Click(object sender, EventArgs e)
{
_mainForm.InvokeIfRequired(async form =>
{
var fileSystemWatchers = new List<FileSystemWatcherState>();
var watchPaths = new HashSet<string>();
// Temporary disable all filesystem watchers that are watching the selected file directory.
foreach (var row in GetSelectedDataGridViewRows(dataGridView1))
{
var path = (string)row.Cells["PathColumn"].Value;
foreach (var fileSystemWatcher in form.FileSystemWatchers)
{
if (!path.IsPathEqual(fileSystemWatcher.Path) &&
!path.IsSubPathOf(fileSystemWatcher.Path))
{
continue;
}
if (watchPaths.Contains(fileSystemWatcher.Path))
{
continue;
}
fileSystemWatchers.Add(new FileSystemWatcherState(fileSystemWatcher));
fileSystemWatcher.EnableRaisingEvents = false;
watchPaths.Add(fileSystemWatcher.Path);
}
}
try
{
var rows = GetSelectedDataGridViewRows(dataGridView1);
var count = rows.Count;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = count;
var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
{
if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
{
Log.Error(rowProgressFailure.Exception, "Could not revert to snapshot.");
toolStripStatusLabel1.Text =
$"Could not revert {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
return;
}
toolStripStatusLabel1.Text =
$"Reverted {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
});
await Task.Run(() => RevertFile(rows, progress, _cancellationToken), _cancellationToken);
if (_cancellationToken.IsCancellationRequested)
{
toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
toolStripStatusLabel1.Text = "Done.";
}
}
catch (Exception exception)
{
Log.Error(exception, "Could not update data grid view.");
}
finally
{
// Restore initial state.
foreach (var fileSystemWatcherState in fileSystemWatchers)
{
foreach (var fileSystemWatcher in form.FileSystemWatchers)
{
if (fileSystemWatcherState.FileSystemWatcher == fileSystemWatcher)
{
fileSystemWatcher.EnableRaisingEvents = fileSystemWatcherState.State;
}
}
}
}
});
}
private async void SnapshotManagerForm_Load(object sender, EventArgs e)
{
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = (int)await _snapshotDatabase.CountSnapshots(_cancellationToken);
var snapshotQueue = new ConcurrentQueue<Snapshot>();
var snapshotsQueuedTaskCompletionSource = new TaskCompletionSource<object>();
async void IdleHandler(object idleHandlerSender, EventArgs idleHandlerArgs)
{
await snapshotsQueuedTaskCompletionSource.Task;
try
{
if (!snapshotQueue.TryDequeue(out var snapshot))
{
Application.Idle -= IdleHandler;
dataGridView1.Sort(dataGridView1.Columns["TimeColumn"], ListSortDirection.Descending);
toolStripStatusLabel1.Text = "Done.";
return;
}
var index = dataGridView1.Rows.Add();
dataGridView1.Rows[index].Cells["TimeColumn"].Value = DateTime.Parse(snapshot.Time);
dataGridView1.Rows[index].Cells["NameColumn"].Value = snapshot.Name;
dataGridView1.Rows[index].Cells["PathColumn"].Value = snapshot.Path;
dataGridView1.Rows[index].Cells["HashColumn"].Value = snapshot.Hash;
dataGridView1.Rows[index].DefaultCellStyle.BackColor = snapshot.Color;
toolStripStatusLabel1.Text = $"Loaded {snapshot.Name}...";
toolStripProgressBar1.Increment(1);
statusStrip1.Update();
}
catch (Exception exception)
{
Log.Error(exception, "Could not update data grid view.");
}
}
Application.Idle += IdleHandler;
try
{
foreach (var snapshot in await _snapshotDatabase.LoadSnapshots(_cancellationToken))
{
snapshotQueue.Enqueue(snapshot);
}
snapshotsQueuedTaskCompletionSource.TrySetResult(new { });
}
catch (Exception exception)
{
Application.Idle -= IdleHandler;
Log.Error(exception, "Unable to load snapshots.");
}
}
private void SnapshotManagerForm_Closing(object sender, FormClosingEventArgs e)
{
_cancellationTokenSource.Cancel();
if (_snapshotPreviewForm != null)
{
_snapshotPreviewForm.Close();
_snapshotPreviewForm = null;
}
if (_hexViewForm != null)
{
_hexViewForm.Close();
_hexViewForm = null;
}
}
private void DataGridView1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
return;
}
e.Effect = DragDropEffects.None;
}
private async void DataGridView1_DragDrop(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
return;
}
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = files.Length;
toolStripStatusLabel1.Text = "Snapshotting files...";
var screenCapture = ScreenCapture.Capture((CaptureMode)_mainForm.Configuration.CaptureMode);
for (var i = 0; i < files.Length; ++i)
{
var color = Color.Empty;
if (_mainForm.TrackedFolders.TryGet(files[i], out var folder))
{
color = folder.Color;
}
if (Directory.Exists(files[i]))
{
foreach (var directoryFile in Directory.GetFiles(files[i], "*.*", SearchOption.AllDirectories))
{
var name = Path.GetFileName(directoryFile);
var path = Path.Combine(Path.GetDirectoryName(directoryFile), name);
try
{
await _snapshotDatabase.CreateSnapshot(name, path, screenCapture, color,
_cancellationToken);
toolStripProgressBar1.Value = i + 1;
toolStripStatusLabel1.Text = $"Snapshot taken of {files[i]}.";
statusStrip1.Update();
}
catch (SQLiteException exception)
{
if (exception.ResultCode == SQLiteErrorCode.Constraint)
{
Log.Information(exception, "Snapshot already exists.");
toolStripProgressBar1.Value = i + 1;
toolStripStatusLabel1.Text = "Snapshot already exists.";
statusStrip1.Update();
}
}
catch (Exception exception)
{
Log.Warning(exception, "Could not create snapshot.");
}
}
continue;
}
var fileName = Path.GetFileName(files[i]);
var pathName = Path.Combine(Path.GetDirectoryName(files[i]), fileName);
try
{
await _snapshotDatabase.CreateSnapshot(fileName, pathName, screenCapture, color,
_cancellationToken);
toolStripProgressBar1.Value = i + 1;
toolStripStatusLabel1.Text = $"Snapshot taken of {files[i]}.";
statusStrip1.Update();
}
catch (SQLiteException exception)
{
if (exception.ResultCode == SQLiteErrorCode.Constraint)
{
Log.Information(exception, "Snapshot already exists.");
toolStripProgressBar1.Value = i + 1;
toolStripStatusLabel1.Text = "Snapshot already exists.";
statusStrip1.Update();
}
}
catch (Exception exception)
{
Log.Warning(exception, "Could not create snapshot");
}
}
}
private async void FileToolStripMenuItem_Click(object sender, EventArgs e)
{
var dialog = new CommonOpenFileDialog();
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
{
var screenCapture = ScreenCapture.Capture((CaptureMode)_mainForm.Configuration.CaptureMode);
var fileName = Path.GetFileName(dialog.FileName);
var directory = Path.GetDirectoryName(dialog.FileName);
var pathName = Path.Combine(directory, fileName);
var color = Color.Empty;
if (_mainForm.TrackedFolders.TryGet(directory, out var folder))
{
color = folder.Color;
}
try
{
await _snapshotDatabase.CreateSnapshot(fileName, pathName, screenCapture, color,
_cancellationToken);
}
catch (SQLiteException exception)
{
if (exception.ResultCode == SQLiteErrorCode.Constraint)
{
Log.Information(exception, "Snapshot already exists.");
}
}
catch (Exception exception)
{
Log.Warning(exception, "Could not create snapshot.");
}
}
}
private async void DirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
var dialog = new CommonOpenFileDialog { IsFolderPicker = true };
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
{
var screenCapture = ScreenCapture.Capture((CaptureMode)_mainForm.Configuration.CaptureMode);
foreach (var directoryFile in Directory.GetFiles(dialog.FileName, "*.*", SearchOption.AllDirectories))
{
var name = Path.GetFileName(directoryFile);
var directory = Path.GetDirectoryName(directoryFile);
var path = Path.Combine(directory, name);
var color = Color.Empty;
if (_mainForm.TrackedFolders.TryGet(directory, out var folder))
{
color = folder.Color;
}
try
{
await _snapshotDatabase.CreateSnapshot(name, path, screenCapture, color, _cancellationToken);
}
catch (SQLiteException exception)
{
if (exception.ResultCode == SQLiteErrorCode.Constraint)
{
Log.Information(exception, "Snapshot already exists.");
}
}
catch (Exception exception)
{
Log.Warning(exception, "Could not create snapshot.");
}
}
}
}
private async void RelocateToolStripMenuItem_Click(object sender, EventArgs e)
{
var commonOpenFileDialog = new CommonOpenFileDialog
{
InitialDirectory = _mainForm.Configuration.LastFolder,
IsFolderPicker = true
};
if (commonOpenFileDialog.ShowDialog() != CommonFileDialogResult.Ok)
{
return;
}
_mainForm.Configuration.LastFolder = commonOpenFileDialog.FileName;
_mainForm.ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
async () => await _mainForm.SaveConfiguration(), _cancellationToken);
var directory = commonOpenFileDialog.FileName;
var rows = GetSelectedDataGridViewRows(dataGridView1);
var count = rows.Count;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = count;
var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
{
var path = Path.Combine(directory,
(string)rowProgress.Row.Cells["NameColumn"].Value);
if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
{
Log.Error(rowProgressFailure.Exception, "Could not relocate snapshot.");
toolStripStatusLabel1.Text =
$"Could not relocate {rowProgress.Row.Cells["NameColumn"].Value} to {path}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
return;
}
rowProgress.Row.Cells["PathColumn"].Value = path;
toolStripStatusLabel1.Text =
$"Relocated {rowProgress.Row.Cells["NameColumn"].Value} to {path}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
});
await Task.Run(() => RelocateFiles(rows, directory, progress, _cancellationToken), _cancellationToken);
if (_cancellationToken.IsCancellationRequested)
{
toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
toolStripStatusLabel1.Text = "Done.";
}
}
private async void NoteToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_snapshotNote != null)
{
return;
}
var row = GetSelectedDataGridViewRows(dataGridView1).FirstOrDefault();
if (row == null)
{
return;
}
try
{
var snapshotPreview = await _snapshotDatabase.RetrievePreview(
(string)row.Cells["HashColumn"].Value, _cancellationToken);
if (snapshotPreview == null)
{
return;
}
_snapshotNote = new SnapshotNoteForm(this, snapshotPreview);
_snapshotNote.Owner = this;
_snapshotNote.SaveNote += SnapshotNote_SaveNote;
_snapshotNote.Closing += SnapshotNote_Closing;
_snapshotNote.Show();
}
catch (Exception exception)
{
Log.Error(exception, "Could not open notes form.");
}
}
private async void SnapshotNote_SaveNote(object sender, SaveNoteEventArgs e)
{
var rows = GetSelectedDataGridViewRows(dataGridView1);
var count = rows.Count;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = count;
var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
{
if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
{
Log.Error(rowProgressFailure.Exception, "Could not update note for snapshot.");
toolStripStatusLabel1.Text =
$"Could not update note for {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
return;
}
toolStripStatusLabel1.Text =
$"Updated note for {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
});
await Task.Run(() => UpdateNote(rows, e.Note, progress, _cancellationToken), _cancellationToken);
if (_cancellationToken.IsCancellationRequested)
{
toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
toolStripStatusLabel1.Text = "Done.";
}
}
private void SnapshotNote_Closing(object sender, CancelEventArgs e)
{
if (_snapshotNote == null)
{
return;
}
_snapshotNote.Closing -= SnapshotNote_Closing;
_snapshotNote.Close();
_snapshotNote = null;
}
private async void ViewHexToolStripMenuItem_Click(object sender, EventArgs e)
{
var rows = GetSelectedDataGridViewRows(dataGridView1);
var row = rows.FirstOrDefault();
if (row == null)
{
return;
}
var hash = (string)row.Cells["HashColumn"].Value;
using (var memoryStream = await _snapshotDatabase.RetrieveFileStream(hash, _cancellationToken))
{
if (memoryStream == null)
{
return;
}
if (_hexViewForm != null)
{
_hexViewForm.UpdateData(memoryStream.ToArray());
_hexViewForm.Activate();
return;
}
_hexViewForm = new HexViewForm(_snapshotDatabase, hash, memoryStream.ToArray());
_hexViewForm.Owner = this;
_hexViewForm.Closing += HexViewForm_Closing;
_hexViewForm.SaveData += HexViewForm_SaveData;
_hexViewForm.Show();
}
}
private async void HexViewForm_SaveData(object sender, SaveDataEventArgs e)
{
var hash = await _snapshotDatabase.UpdateFile(e.Hash, e.Data, _cancellationToken);
if (string.IsNullOrEmpty(hash))
{
return;
}
dataGridView1.InvokeIfRequired(dataGridView =>
{
// Update the hash in the datagridview.
var removeRows = new List<DataGridViewRow>();
foreach (var row in dataGridView.Rows.OfType<DataGridViewRow>())
{
if ((string)row.Cells["HashColumn"].Value == hash)
{
removeRows.Add(row);
}
if ((string)row.Cells["HashColumn"].Value != e.Hash)
{
continue;
}
row.Cells["HashColumn"].Value = hash;
}
// Remove rows that might have the same hash.
foreach (var row in removeRows)
{
dataGridView.Rows.Remove(row);
}
});
}
private void HexViewForm_Closing(object sender, CancelEventArgs e)
{
if (_hexViewForm == null)
{
return;
}
_hexViewForm.SaveData -= HexViewForm_SaveData;
_hexViewForm.Closing -= HexViewForm_Closing;
_hexViewForm.Close();
_hexViewForm = null;
}
private async void FileToolStripMenuItem2_Click(object sender, EventArgs e)
{
var commonOpenFileDialog = new CommonOpenFileDialog
{
InitialDirectory = _mainForm.Configuration.LastFolder,
IsFolderPicker = true
};
if (commonOpenFileDialog.ShowDialog() == CommonFileDialogResult.Ok)
{
_mainForm.Configuration.LastFolder = commonOpenFileDialog.FileName;
_mainForm.ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
async () => await _mainForm.SaveConfiguration(), _cancellationToken);
var directory = commonOpenFileDialog.FileName;
var rows = GetSelectedDataGridViewRows(dataGridView1);
var count = rows.Count;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = count;
var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
{
var fileInfo =
new FileInfo((string)rowProgress.Row.Cells["NameColumn"].Value);
var file = fileInfo.Name;
var path = Path.Combine(directory, file);
if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
{
Log.Error(rowProgressFailure.Exception, "Could not save snapshot.");
toolStripStatusLabel1.Text =
$"Could not save snapshot {rowProgress.Row.Cells["NameColumn"].Value} to {path}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
return;
}
toolStripStatusLabel1.Text =
$"Saved {rowProgress.Row.Cells["NameColumn"].Value} to {path}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
});
await Task.Run(() => SaveFilesTo(rows, directory, progress, _cancellationToken), _cancellationToken);
if (_cancellationToken.IsCancellationRequested)
{
toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
toolStripStatusLabel1.Text = "Done.";
}
}
}
private async void DirectoryToolStripMenuItem2_Click(object sender, EventArgs e)
{
var select = GetSelectedDataGridViewRows(dataGridView1).FirstOrDefault();
if (select == null)
{
return;
}
// C:\aa\bbb\dd.txt
var path = (string)select.Cells["PathColumn"].Value;
// C:\aa\bbb\
var basePath = Path.GetDirectoryName(path);
var dialog = new CommonOpenFileDialog { IsFolderPicker = true };
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
{
//Log.Information(dialog.FileName);
var rows = GetAllDataGridViewRows(dataGridView1);
var count = rows.Count;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = count;
var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
{
if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
{
Log.Error(rowProgressFailure.Exception, "Could not save file.");
toolStripStatusLabel1.Text =
$"Could not save file {rowProgress.Row.Cells["NameColumn"].Value} to {basePath}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
return;
}
toolStripStatusLabel1.Text =
$"Saved {rowProgress.Row.Cells["NameColumn"].Value} to {basePath}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
});
await Task.Run(() => SaveDirectoryTo(rows, basePath, dialog.FileName, progress, _cancellationToken),
_cancellationToken);
if (_cancellationToken.IsCancellationRequested)
{
toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
toolStripStatusLabel1.Text = "Done.";
}
}
}
private void TextBox1_TextChanged(object sender, EventArgs e)
{
_searchTextBoxChangedContinuation.Schedule(TimeSpan.FromSeconds(1), () =>
{
textBox1.InvokeIfRequired(textBox =>
{
var search = textBox.Text;
dataGridView1.InvokeIfRequired(dataGridView =>
{
foreach (var row in GetAllDataGridViewRows(dataGridView))
{
if(row.Cells["PathColumn"].Value == null)
{
continue;
}
switch (((string)row.Cells["PathColumn"].Value).IndexOf(search,
StringComparison.OrdinalIgnoreCase))
{
case -1:
row.Visible = false;
break;
default:
row.Visible = true;
break;
}
}
});
});
}, _cancellationToken);
}
private async void RecomputeHashesToolStripMenuItem1_Click(object sender, EventArgs e)
{
var rows = GetSelectedDataGridViewRows(dataGridView1);
var count = rows.Count;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = count;
var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
{
if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
{
Log.Error(rowProgressFailure.Exception, "Could not recompute hash for snapshot.");
toolStripStatusLabel1.Text =
$"Could not recompute hash for {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
return;
}
toolStripStatusLabel1.Text =
$"Recomputed hash for {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
});
await Task.Run(() => RecomputeHashes(rows, progress, _cancellationToken), _cancellationToken);
if (_cancellationToken.IsCancellationRequested)
{
toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
toolStripStatusLabel1.Text = "Done.";
}
}
private async void NormalizeDateTimeToolStripMenuItem_Click(object sender, EventArgs e)
{
var rows = GetSelectedDataGridViewRows(dataGridView1);
var count = rows.Count;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = count;
var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
{
if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
{
Log.Error(rowProgressFailure.Exception, "Could not normalize date-time for snapshot.");
toolStripStatusLabel1.Text =
$"Could not normalize date-time for {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
return;
}
toolStripStatusLabel1.Text =
$"Normalized date-time for {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
});
await Task.Run(() => NormalizeDateTime(rows, progress, _cancellationToken), _cancellationToken);
if (_cancellationToken.IsCancellationRequested)
{
toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
toolStripStatusLabel1.Text = "Done.";
}
}
#endregion
#region Private Methods
private async Task DeleteFiles(IReadOnlyList<DataGridViewRow> rows, IProgress<DataGridViewRowProgress> progress,
CancellationToken cancellationToken)
{
var count = rows.Count;
for (var index = 0; index < count && !cancellationToken.IsCancellationRequested; ++index)
{
try
{
await _snapshotDatabase.RemoveFile((string)rows[index].Cells["HashColumn"].Value,
cancellationToken);
progress.Report(new DataGridViewRowProgressSuccess(rows[index], index));
}
catch (Exception exception)
{
progress.Report(new DataGridViewRowProgressFailure(rows[index], index, exception));
}
}
}
private async Task DeleteFilesFast(IReadOnlyList<DataGridViewRow> rows, CancellationToken cancellationToken)
{
var hashes = rows.Select(row => (string)row.Cells["HashColumn"].Value);
await _snapshotDatabase.RemoveFileFast(hashes, cancellationToken);
}
private async Task UpdateNote(IReadOnlyList<DataGridViewRow> rows, string note,
IProgress<DataGridViewRowProgress> progress, CancellationToken cancellationToken)
{
var count = rows.Count;
for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
{
try
{
await _snapshotDatabase.UpdateNote((string)rows[i].Cells["HashColumn"].Value, note,
cancellationToken);
progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
}
catch (Exception exception)
{
progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
}
}
}
private static List<DataGridViewRow> GetSelectedDataGridViewRows(DataGridView dataGridView)
{
return dataGridView.SelectedRows.OfType<DataGridViewRow>().ToList();
}
private static List<DataGridViewRow> GetAllDataGridViewRows(DataGridView dataGridView)
{
return dataGridView.Rows.OfType<DataGridViewRow>().ToList();
}
private async Task RemoveColorFiles(IReadOnlyList<DataGridViewRow> rows,
IProgress<DataGridViewRowProgress> progress,
CancellationToken cancellationToken)
{
var count = rows.Count;
for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
{
try
{
await _snapshotDatabase.RemoveColor((string)rows[i].Cells["HashColumn"].Value,
cancellationToken);
progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
}
catch (Exception exception)
{
progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
}
}
}
private async Task ColorFiles(IReadOnlyList<DataGridViewRow> rows, Color color,
IProgress<DataGridViewRowProgress> progress, CancellationToken cancellationToken)
{
var count = rows.Count;
for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
{
try
{
await _snapshotDatabase.UpdateColor((string)rows[i].Cells["HashColumn"].Value, color,
cancellationToken);
progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
}
catch (Exception exception)
{
progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
}
}
}
private async Task RevertFile(IReadOnlyList<DataGridViewRow> rows, IProgress<DataGridViewRowProgress> progress,
CancellationToken cancellationToken)
{
var count = rows.Count;
for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
{
try
{
await _snapshotDatabase.RevertFile((string)rows[i].Cells["NameColumn"].Value,
(string)rows[i].Cells["HashColumn"].Value,
cancellationToken, _mainForm.Configuration.AtomicOperations);
progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
}
catch (Exception exception)
{
progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
}
}
}
private async void SaveFilesTo(IReadOnlyList<DataGridViewRow> rows, string directory,
IProgress<DataGridViewRowProgress> progress, CancellationToken cancellationToken)
{
var count = rows.Count;
for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
{
try
{
var fileInfo = new FileInfo((string)rows[i].Cells["NameColumn"].Value);
var file = fileInfo.Name;
var path = Path.Combine(directory, file);
await _snapshotDatabase.SaveFile(path, (string)rows[i].Cells["HashColumn"].Value,
cancellationToken);
progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
}
catch (Exception exception)
{
progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
}
}
}
private async Task RelocateFiles(IReadOnlyList<DataGridViewRow> rows, string directory,
IProgress<DataGridViewRowProgress> progress,
CancellationToken cancellationToken)
{
var count = rows.Count;
for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
{
try
{
var path = Path.Combine(directory, (string)rows[i].Cells["NameColumn"].Value);
await _snapshotDatabase.RelocateFile((string)rows[i].Cells["HashColumn"].Value, path,
cancellationToken);
progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
}
catch (Exception exception)
{
progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
}
}
}
private async void RecomputeHashes(IReadOnlyList<DataGridViewRow> rows,
IProgress<DataGridViewRowProgress> progress,
CancellationToken cancellationToken)
{
var count = rows.Count;
for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
{
try
{
using (var memoryStream =
await _snapshotDatabase.RetrieveFileStream((string)rows[i].Cells["HashColumn"].Value,
cancellationToken))
{
if (memoryStream == null)
{
continue;
}
using (var md5 = MD5.Create())
{
var recomputedHash = md5.ComputeHash(memoryStream);
var hashHex = BitConverter.ToString(recomputedHash).Replace("-", "")
.ToLowerInvariant();
await _snapshotDatabase.UpdateHash((string)rows[i].Cells["HashColumn"].Value, hashHex,
cancellationToken);
rows[i].Cells["HashColumn"].Value = hashHex;
progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
}
}
}
catch (Exception exception)
{
progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
}
}
}
private async Task SaveDirectoryTo(IReadOnlyList<DataGridViewRow> rows, string basePath, string targetPath,
IProgress<DataGridViewRowProgress> progress,
CancellationToken cancellationToken)
{
var store = new HashSet<string>();
var count = rows.Count;
for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
{
try
{
// C:\aa\bbb\fff\gg.txt
var rowPath = (string)rows[i].Cells["PathColumn"].Value;
if (store.Contains(rowPath))
{
continue;
}
// C:\aa\bbb\fff\gg.txt subpath C:\aa\bbb\
if (!rowPath.IsPathEqual(basePath) &&
!rowPath.IsSubPathOf(basePath))
{
continue;
}
var rootPath = new DirectoryInfo(basePath).Name;
var relPath = rowPath.Remove(0, basePath.Length).Trim('\\');
var newPath = Path.Combine(targetPath, rootPath, relPath);
var hash = (string)rows[i].Cells["HashColumn"].Value;
await _snapshotDatabase.SaveFile(newPath, hash, cancellationToken);
progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
if (!store.Contains(rowPath))
{
store.Add(rowPath);
}
}
catch (Exception exception)
{
progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
}
}
}
private async Task NormalizeDateTime(IReadOnlyList<DataGridViewRow> rows,
IProgress<DataGridViewRowProgress> progress,
CancellationToken cancellationToken)
{
var count = rows.Count;
for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
{
try
{
await _snapshotDatabase.NormalizeTime((string)rows[i].Cells["HashColumn"].Value,
cancellationToken);
progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
}
catch (Exception exception)
{
progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
}
}
}
private async void deleteToolStripMenuItem1_Click(object sender, EventArgs e)
{
var toolStripMenuItem = (ToolStripMenuItem)sender;
var rows = GetSelectedDataGridViewRows(dataGridView1);
var count = rows.Count;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = count;
var progress = new Progress<DataGridViewRowProgress>(rowProgress =>
{
if (rowProgress is DataGridViewRowProgressFailure rowProgressFailure)
{
Log.Error(rowProgressFailure.Exception, "Unable to delete screenshot.");
toolStripStatusLabel1.Text =
$"Could not delete screenshot for {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
return;
}
toolStripStatusLabel1.Text =
$"Colored {rowProgress.Row.Cells["NameColumn"].Value}...";
toolStripProgressBar1.Value = rowProgress.Index + 1;
statusStrip1.Update();
});
await Task.Run(() => DeleteScreenshots(rows, progress, _cancellationToken), _cancellationToken);
if (_cancellationToken.IsCancellationRequested)
{
toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
toolStripStatusLabel1.Text = "Done.";
}
}
private async Task DeleteScreenshots(IReadOnlyList<DataGridViewRow> rows,
IProgress<DataGridViewRowProgress> progress,
CancellationToken cancellationToken)
{
var count = rows.Count;
for (var i = 0; i < count && !cancellationToken.IsCancellationRequested; ++i)
{
try
{
await _snapshotDatabase.DeleteScreenshot((string)rows[i].Cells["HashColumn"].Value,
cancellationToken);
progress.Report(new DataGridViewRowProgressSuccess(rows[i], i));
}
catch (Exception exception)
{
progress.Report(new DataGridViewRowProgressFailure(rows[i], i, exception));
}
}
}
#endregion
private void DataGridView1_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
{
DataGridView dataGridView = sender as DataGridView;
foreach (DataGridViewCell cell in dataGridView.Rows[e.RowIndex].Cells)
{
if (cell.Selected == false) { continue; }
var bgColorCell = Color.White;
if (cell.Style.BackColor != Color.Empty) { bgColorCell = cell.Style.BackColor; }
else if (cell.InheritedStyle.BackColor != Color.Empty) { bgColorCell = cell.InheritedStyle.BackColor; }
cell.Style.SelectionBackColor = MixColor(bgColorCell, Color.FromArgb(0, 150, 255), 10, 4);
}
}
//Mix two colors
//Example: Steps=10 & Position=4 makes Color2 mix 40% into Color1
/// <summary>
/// Mix two colors.
/// </summary>
/// <param name="Color1"></param>
/// <param name="Color2"></param>
/// <param name="Steps"></param>
/// <param name="Position"></param>
/// <example>Steps=10 & Positon=4 makes Color2 mix 40% into Color1</example>
/// <remarks>https://stackoverflow.com/questions/38337849/transparent-selectionbackcolor-for-datagridview-cell</remarks>
/// <returns></returns>
public static Color MixColor(Color Color1, Color Color2, int Steps, int Position)
{
if (Position <= 0 || Steps <= 1) { return Color1; }
if (Position >= Steps) { return Color2; }
return Color.FromArgb(
Color1.R + ((Color2.R - Color1.R) / Steps * Position),
Color1.G + ((Color2.G - Color1.G) / Steps * Position),
Color1.B + ((Color2.B - Color1.B) / Steps * Position)
);
}
}
}
Generated by GNU Enscript 1.6.5.90.