Horizon

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 4  →  ?path2? @ 5
/Configuration/Configuration.csproj
@@ -21,7 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>7.3</LangVersion>
<LangVersion>8</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -30,7 +30,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>7.3</LangVersion>
<LangVersion>8</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
/Horizon/Database/SnapshotDatabase.cs
@@ -6,6 +6,7 @@
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
@@ -96,22 +97,29 @@
#region Private Delegates, Events, Enums, Properties, Indexers and Fields
 
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly SemaphoreSlim _databaseLock;
 
private SemaphoreSlim _snapshotSemaphore;
 
#endregion
 
#region Constructors, Destructors and Finalizers
 
public SnapshotDatabase()
private SnapshotDatabase()
{
Directory.CreateDirectory(Constants.DatabaseDirectory);
 
_databaseLock = new SemaphoreSlim(1, 1);
}
 
public SnapshotDatabase(CancellationToken cancellationToken) : this()
{
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
var localCancellationToken = _cancellationTokenSource.Token;
var combinedCancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(localCancellationToken, cancellationToken);
_cancellationToken = combinedCancellationTokenSource.Token;
 
_snapshotSemaphore = new SemaphoreSlim(1, 1);
 
Directory.CreateDirectory(Constants.DatabaseDirectory);
 
CreateDatabase(_cancellationToken).ContinueWith(async createDatabaseTask =>
{
try
@@ -137,9 +145,6 @@
public void Dispose()
{
_cancellationTokenSource.Cancel();
 
_snapshotSemaphore?.Dispose();
_snapshotSemaphore = null;
}
 
#endregion
@@ -153,17 +158,18 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveScreenshotFromHashSql, sqliteConnection, dbTransaction))
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
try
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveScreenshotFromHashSql, sqliteConnection, dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
{
@@ -172,19 +178,26 @@
 
sqliteCommand.Prepare();
 
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
throw;
}
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task NormalizeTime(string hash, CancellationToken cancellationToken)
@@ -194,71 +207,80 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var readSQLiteCommand = new SQLiteCommand(RetrieveTimeFromHash, sqliteConnection))
{
readSQLiteCommand.Parameters.AddRange(new[]
using (var readSQLiteCommand = new SQLiteCommand(RetrieveTimeFromHash, sqliteConnection))
{
new SQLiteParameter("@hash", hash)
});
readSQLiteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
 
readSQLiteCommand.Prepare();
readSQLiteCommand.Prepare();
 
using (var sqlDataReader = await readSQLiteCommand.ExecuteReaderAsync(cancellationToken))
{
using (var dbTransaction = sqliteConnection.BeginTransaction())
using (var sqlDataReader = await readSQLiteCommand.ExecuteReaderAsync(cancellationToken))
{
try
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
while (await sqlDataReader.ReadAsync(cancellationToken))
try
{
var time = (string)sqlDataReader["Time"];
 
// Skip if already ISO 8601
if (DateTime.TryParseExact(time,
"yyyy-MM-ddTHH:mm:ss.fff",
CultureInfo.InvariantCulture,
DateTimeStyles.None, out _))
while (await sqlDataReader.ReadAsync(cancellationToken))
{
continue;
}
var time = (string)sqlDataReader["Time"];
 
if (!DateTime.TryParse(time, out var dateTime))
{
dateTime = DateTime.Now;
}
// Skip if already ISO 8601
if (DateTime.TryParseExact(time,
"yyyy-MM-ddTHH:mm:ss.fff",
CultureInfo.InvariantCulture,
DateTimeStyles.None, out _))
{
continue;
}
 
using (var writeSQLiteCommand =
new SQLiteCommand(UpdateTimeFromHash, sqliteConnection, dbTransaction))
{
writeSQLiteCommand.Parameters.AddRange(new[]
if (!DateTime.TryParse(time, out var dateTime))
{
new SQLiteParameter("@time", dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fff")),
new SQLiteParameter("@hash", hash)
});
dateTime = DateTime.Now;
}
 
writeSQLiteCommand.Prepare();
using (var writeSQLiteCommand =
new SQLiteCommand(UpdateTimeFromHash, sqliteConnection, dbTransaction))
{
writeSQLiteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@time",
dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fff")),
new SQLiteParameter("@hash", hash)
});
 
await writeSQLiteCommand.ExecuteNonQueryAsync(cancellationToken);
writeSQLiteCommand.Prepare();
 
await writeSQLiteCommand.ExecuteNonQueryAsync(cancellationToken);
}
}
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
dbTransaction.Commit();
throw;
}
}
catch
{
dbTransaction.Rollback();
 
throw;
}
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task<long> CountSnapshots(CancellationToken cancellationToken)
@@ -268,37 +290,45 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(CountSnapshotsSql, sqliteConnection))
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
long count = 0;
await sqliteConnection.OpenAsync(cancellationToken);
 
sqliteCommand.Prepare();
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(CountSnapshotsSql, sqliteConnection))
{
long count = 0;
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
sqliteCommand.Prepare();
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
if (!(sqlDataReader[0] is long dbCount))
while (await sqlDataReader.ReadAsync(cancellationToken))
{
count = -1;
break;
if (!(sqlDataReader[0] is long dbCount))
{
count = -1;
break;
}
 
count = dbCount;
}
 
count = dbCount;
return count;
}
 
return count;
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task<IEnumerable<Snapshot>> LoadSnapshots(CancellationToken cancellationToken)
public async IAsyncEnumerable<Snapshot> LoadSnapshots([EnumeratorCancellation] CancellationToken cancellationToken)
{
var connectionString = new SQLiteConnectionStringBuilder
{
@@ -305,331 +335,347 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(RetrieveSnapshotsSql, sqliteConnection))
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
sqliteCommand.Prepare();
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(RetrieveSnapshotsSql, sqliteConnection))
{
var snapshots = new List<Snapshot>();
while (await sqlDataReader.ReadAsync(cancellationToken))
sqliteCommand.Prepare();
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
var name = (string)sqlDataReader["Name"];
var path = (string)sqlDataReader["Path"];
var time = (string)sqlDataReader["Time"];
var hash = (string)sqlDataReader["Hash"];
//var snapshots = new List<Snapshot>();
while (await sqlDataReader.ReadAsync(cancellationToken))
{
var name = (string)sqlDataReader["Name"];
var path = (string)sqlDataReader["Path"];
var time = (string)sqlDataReader["Time"];
var hash = (string)sqlDataReader["Hash"];
 
var color = Color.Empty;
var color = Color.Empty;
 
if (!(sqlDataReader["Color"] is DBNull))
{
var dbColor = Convert.ToInt32(sqlDataReader["Color"]);
if (!(sqlDataReader["Color"] is DBNull))
{
var dbColor = Convert.ToInt32(sqlDataReader["Color"]);
 
switch (dbColor)
{
case 0:
color = Color.Empty;
break;
default:
color = Color.FromArgb(dbColor);
break;
switch (dbColor)
{
case 0:
color = Color.Empty;
break;
default:
color = Color.FromArgb(dbColor);
break;
}
}
 
yield return new Snapshot(name, path, time, hash, color);
}
 
snapshots.Add(new Snapshot(name, path, time, hash, color));
}
 
return snapshots;
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task CreateSnapshot(string name, string path, Color color, CancellationToken cancellationToken)
{
await _snapshotSemaphore.WaitAsync(cancellationToken);
 
var connectionString = new SQLiteConnectionStringBuilder
{
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
try
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
using (var md5 = MD5.Create())
try
{
using (var hashMemoryStream = new MemoryStream())
using (var md5 = MD5.Create())
{
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Open, FileAccess.Read,
FileShare.Read,
cancellationToken))
using (var hashMemoryStream = new MemoryStream())
{
fileStream.Position = 0L;
await fileStream.CopyToAsync(hashMemoryStream);
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Open, FileAccess.Read,
FileShare.Read,
cancellationToken))
{
fileStream.Position = 0L;
await fileStream.CopyToAsync(hashMemoryStream);
 
hashMemoryStream.Position = 0L;
var hash = md5.ComputeHash(hashMemoryStream);
var hashHex = BitConverter.ToString(hash).Replace("-", "")
.ToLowerInvariant();
hashMemoryStream.Position = 0L;
var hash = md5.ComputeHash(hashMemoryStream);
var hashHex = BitConverter.ToString(hash).Replace("-", "")
.ToLowerInvariant();
 
using (var fileMemoryStream = new MemoryStream())
{
using (var fileZipStream =
new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
using (var fileMemoryStream = new MemoryStream())
{
fileStream.Position = 0L;
await fileStream.CopyToAsync(fileZipStream);
fileZipStream.Close();
using (var fileZipStream =
new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
{
fileStream.Position = 0L;
await fileStream.CopyToAsync(fileZipStream);
fileZipStream.Close();
 
var time = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fff");
var time = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fff");
 
fileMemoryStream.Position = 0L;
fileMemoryStream.Position = 0L;
 
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(SnapshotFileNoScreenshotSql, sqliteConnection,
dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(SnapshotFileNoScreenshotSql, sqliteConnection,
dbTransaction))
{
new SQLiteParameter("@name", name),
new SQLiteParameter("@time", time),
new SQLiteParameter("@path", path),
new SQLiteParameter("@dataLength",
fileMemoryStream.Length),
new SQLiteParameter("@hash", hashHex)
});
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@name", name),
new SQLiteParameter("@time", time),
new SQLiteParameter("@path", path),
new SQLiteParameter("@dataLength",
fileMemoryStream.Length),
new SQLiteParameter("@hash", hashHex)
});
 
var numeric = color.ToArgb();
switch (numeric)
{
case 0:
sqliteCommand.Parameters.Add(
new SQLiteParameter("@color", null));
break;
default:
sqliteCommand.Parameters.Add(
new SQLiteParameter("@color", numeric));
break;
}
var numeric = color.ToArgb();
switch (numeric)
{
case 0:
sqliteCommand.Parameters.Add(
new SQLiteParameter("@color", null));
break;
default:
sqliteCommand.Parameters.Add(
new SQLiteParameter("@color", numeric));
break;
}
 
sqliteCommand.Prepare();
sqliteCommand.Prepare();
 
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
}
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
}
 
// Insert the data blobs.
using (var sqliteCommand =
new SQLiteCommand(GetLastRowInsertSql, sqliteConnection,
dbTransaction))
{
sqliteCommand.Prepare();
// Insert the data blobs.
using (var sqliteCommand =
new SQLiteCommand(GetLastRowInsertSql, sqliteConnection,
dbTransaction))
{
sqliteCommand.Prepare();
 
var rowId =
(long)await sqliteCommand.ExecuteScalarAsync(cancellationToken);
var rowId =
(long)await sqliteCommand.ExecuteScalarAsync(cancellationToken);
 
using (var sqliteBlob =
SQLiteBlob.Create(sqliteConnection, "main", "Snapshots", "Data",
rowId,
false))
{
var fileMemoryStreamData = fileMemoryStream.ToArray();
using (var sqliteBlob =
SQLiteBlob.Create(sqliteConnection, "main", "Snapshots",
"Data",
rowId,
false))
{
var fileMemoryStreamData = fileMemoryStream.ToArray();
 
sqliteBlob.Write(fileMemoryStreamData, fileMemoryStreamData.Length,
0);
sqliteBlob.Write(fileMemoryStreamData,
fileMemoryStreamData.Length,
0);
}
}
}
 
dbTransaction.Commit();
dbTransaction.Commit();
 
SnapshotCreate?.Invoke(this,
new SnapshotCreateSuccessEventArgs(name, time, path, color,
hashHex));
SnapshotCreate?.Invoke(this,
new SnapshotCreateSuccessEventArgs(name, time, path, color,
hashHex));
}
}
}
}
}
}
}
catch (SQLiteException exception)
{
dbTransaction.Rollback();
catch (SQLiteException exception)
{
dbTransaction.Rollback();
 
if (exception.ResultCode != SQLiteErrorCode.Constraint)
if (exception.ResultCode != SQLiteErrorCode.Constraint)
{
SnapshotCreate?.Invoke(this,
new SnapshotCreateFailureEventArgs(name, path, color, exception));
}
 
throw;
}
catch (Exception exception)
{
dbTransaction.Rollback();
 
SnapshotCreate?.Invoke(this,
new SnapshotCreateFailureEventArgs(name, path, color, exception));
 
throw;
}
 
throw;
}
catch (Exception exception)
{
dbTransaction.Rollback();
 
SnapshotCreate?.Invoke(this, new SnapshotCreateFailureEventArgs(name, path, color, exception));
 
throw;
}
finally
{
_snapshotSemaphore.Release();
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task CreateSnapshot(string name, string path,
Bitmap shot, Color color, CancellationToken cancellationToken)
{
await _snapshotSemaphore.WaitAsync(cancellationToken);
 
var connectionString = new SQLiteConnectionStringBuilder
{
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
try
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
using (var md5 = MD5.Create())
try
{
using (var hashMemoryStream = new MemoryStream())
using (var md5 = MD5.Create())
{
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Open, FileAccess.Read,
FileShare.Read,
cancellationToken))
using (var hashMemoryStream = new MemoryStream())
{
fileStream.Position = 0L;
await fileStream.CopyToAsync(hashMemoryStream);
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Open, FileAccess.Read,
FileShare.Read,
cancellationToken))
{
fileStream.Position = 0L;
await fileStream.CopyToAsync(hashMemoryStream);
 
hashMemoryStream.Position = 0L;
var hash = md5.ComputeHash(hashMemoryStream);
var hashHex = BitConverter.ToString(hash).Replace("-", "")
.ToLowerInvariant();
hashMemoryStream.Position = 0L;
var hash = md5.ComputeHash(hashMemoryStream);
var hashHex = BitConverter.ToString(hash).Replace("-", "")
.ToLowerInvariant();
 
using (var fileMemoryStream = new MemoryStream())
{
using (var fileZipStream =
new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
using (var fileMemoryStream = new MemoryStream())
{
fileStream.Position = 0L;
await fileStream.CopyToAsync(fileZipStream);
fileZipStream.Close();
using (var fileZipStream =
new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
{
fileStream.Position = 0L;
await fileStream.CopyToAsync(fileZipStream);
fileZipStream.Close();
 
using (var bitmapMemoryStream = new MemoryStream())
{
using (var bitmapZipStream =
new GZipStream(bitmapMemoryStream, CompressionMode.Compress,
true))
using (var bitmapMemoryStream = new MemoryStream())
{
shot.Save(bitmapZipStream, ImageFormat.Bmp);
bitmapZipStream.Close();
using (var bitmapZipStream =
new GZipStream(bitmapMemoryStream, CompressionMode.Compress,
true))
{
shot.Save(bitmapZipStream, ImageFormat.Bmp);
bitmapZipStream.Close();
 
var time = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fff");
var time = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fff");
 
fileMemoryStream.Position = 0L;
bitmapMemoryStream.Position = 0L;
fileMemoryStream.Position = 0L;
bitmapMemoryStream.Position = 0L;
 
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(SnapshotFileSql, sqliteConnection,
dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(SnapshotFileSql, sqliteConnection,
dbTransaction))
{
new SQLiteParameter("@name", name),
new SQLiteParameter("@time", time),
new SQLiteParameter("@path", path),
new SQLiteParameter("@shotLength",
bitmapMemoryStream.Length),
new SQLiteParameter("@dataLength",
fileMemoryStream.Length),
new SQLiteParameter("@hash", hashHex)
});
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@name", name),
new SQLiteParameter("@time", time),
new SQLiteParameter("@path", path),
new SQLiteParameter("@shotLength",
bitmapMemoryStream.Length),
new SQLiteParameter("@dataLength",
fileMemoryStream.Length),
new SQLiteParameter("@hash", hashHex)
});
 
var numeric = color.ToArgb();
switch (numeric)
{
case 0:
sqliteCommand.Parameters.Add(
new SQLiteParameter("@color", null));
break;
default:
sqliteCommand.Parameters.Add(
new SQLiteParameter("@color", numeric));
break;
var numeric = color.ToArgb();
switch (numeric)
{
case 0:
sqliteCommand.Parameters.Add(
new SQLiteParameter("@color", null));
break;
default:
sqliteCommand.Parameters.Add(
new SQLiteParameter("@color", numeric));
break;
}
 
sqliteCommand.Prepare();
 
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
}
 
sqliteCommand.Prepare();
// Insert the data blobs.
using (var sqliteCommand =
new SQLiteCommand(GetLastRowInsertSql, sqliteConnection,
dbTransaction))
{
sqliteCommand.Prepare();
 
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
}
var rowId =
(long)await sqliteCommand.ExecuteScalarAsync(
cancellationToken);
 
// Insert the data blobs.
using (var sqliteCommand =
new SQLiteCommand(GetLastRowInsertSql, sqliteConnection,
dbTransaction))
{
sqliteCommand.Prepare();
using (var sqliteBlob =
SQLiteBlob.Create(sqliteConnection, "main",
"Snapshots",
"Data",
rowId,
false))
{
var fileMemoryStreamData = fileMemoryStream.ToArray();
 
var rowId =
(long)await sqliteCommand.ExecuteScalarAsync(
cancellationToken);
sqliteBlob.Write(fileMemoryStreamData,
fileMemoryStreamData.Length,
0);
}
 
using (var sqliteBlob =
SQLiteBlob.Create(sqliteConnection, "main", "Snapshots",
"Data",
rowId,
false))
{
var fileMemoryStreamData = fileMemoryStream.ToArray();
using (var sqliteBlob =
SQLiteBlob.Create(sqliteConnection, "main",
"Snapshots",
"Shot",
rowId,
false))
{
var bitmapMemoryStreamData =
bitmapMemoryStream.ToArray();
 
sqliteBlob.Write(fileMemoryStreamData,
fileMemoryStreamData.Length,
0);
sqliteBlob.Write(bitmapMemoryStreamData,
bitmapMemoryStreamData.Length,
0);
}
}
 
using (var sqliteBlob =
SQLiteBlob.Create(sqliteConnection, "main", "Snapshots",
"Shot",
rowId,
false))
{
var bitmapMemoryStreamData = bitmapMemoryStream.ToArray();
dbTransaction.Commit();
 
sqliteBlob.Write(bitmapMemoryStreamData,
bitmapMemoryStreamData.Length,
0);
}
SnapshotCreate?.Invoke(this,
new SnapshotCreateSuccessEventArgs(name, time, path, color,
hashHex));
}
 
dbTransaction.Commit();
 
SnapshotCreate?.Invoke(this,
new SnapshotCreateSuccessEventArgs(name, time, path, color,
hashHex));
}
}
}
@@ -637,33 +683,34 @@
}
}
}
}
catch (SQLiteException exception)
{
dbTransaction.Rollback();
catch (SQLiteException exception)
{
dbTransaction.Rollback();
 
if (exception.ResultCode != SQLiteErrorCode.Constraint)
if (exception.ResultCode != SQLiteErrorCode.Constraint)
{
SnapshotCreate?.Invoke(this,
new SnapshotCreateFailureEventArgs(name, path, color, exception));
}
 
throw;
}
catch (Exception exception)
{
dbTransaction.Rollback();
 
SnapshotCreate?.Invoke(this,
new SnapshotCreateFailureEventArgs(name, path, color, exception));
 
throw;
}
 
throw;
}
catch (Exception exception)
{
dbTransaction.Rollback();
 
SnapshotCreate?.Invoke(this, new SnapshotCreateFailureEventArgs(name, path, color, exception));
 
throw;
}
finally
{
_snapshotSemaphore.Release();
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task SaveFile(string path, string hash, CancellationToken cancellationToken)
@@ -673,45 +720,49 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RetrieveDataPathFromHashSql, sqliteConnection))
{
sqliteCommand.Parameters.AddRange(new[]
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RetrieveDataPathFromHashSql, sqliteConnection))
{
new SQLiteParameter("@hash", hash)
});
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
 
sqliteCommand.Prepare();
sqliteCommand.Prepare();
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
// Create directories if they do not exist.
var dir = Path.GetDirectoryName(path);
 
if (dir != null && !Directory.Exists(dir))
while (await sqlDataReader.ReadAsync(cancellationToken))
{
Directory.CreateDirectory(dir);
}
// Create directories if they do not exist.
var dir = Path.GetDirectoryName(path);
 
using (var readStream = sqlDataReader.GetStream(2))
{
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Create, FileAccess.Write,
FileShare.Write,
cancellationToken))
if (dir != null && !Directory.Exists(dir))
{
readStream.Position = 0L;
Directory.CreateDirectory(dir);
}
 
using (var zipStream = new GZipStream(readStream, CompressionMode.Decompress))
using (var readStream = sqlDataReader.GetStream(2))
{
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Create, FileAccess.Write,
FileShare.Write,
cancellationToken))
{
await zipStream.CopyToAsync(fileStream);
readStream.Position = 0L;
 
using (var zipStream = new GZipStream(readStream, CompressionMode.Decompress))
{
await zipStream.CopyToAsync(fileStream);
}
}
}
}
@@ -719,129 +770,135 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task RevertFile(string name, string hash, CancellationToken cancellationToken, bool atomic = true)
{
await _snapshotSemaphore.WaitAsync(cancellationToken);
 
var connectionString = new SQLiteConnectionStringBuilder
{
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RetrieveDataPathFromHashSql, sqliteConnection))
{
try
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RetrieveDataPathFromHashSql, sqliteConnection))
{
sqliteCommand.Parameters.AddRange(new[]
try
{
new SQLiteParameter("@hash", hash)
});
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
 
sqliteCommand.Prepare();
sqliteCommand.Prepare();
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
var path = (string)sqlDataReader["Path"];
while (await sqlDataReader.ReadAsync(cancellationToken))
{
var path = (string)sqlDataReader["Path"];
 
// Create directories if they do not exist.
var dir = Path.GetDirectoryName(path);
// Create directories if they do not exist.
var dir = Path.GetDirectoryName(path);
 
if (dir != null && !Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
if (dir != null && !Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
 
switch (atomic)
{
case true:
// Atomic
var temp = Path.Combine(Path.GetDirectoryName(path),
$"{Path.GetFileName(path)}.temp");
switch (atomic)
{
case true:
// Atomic
var temp = Path.Combine(Path.GetDirectoryName(path),
$"{Path.GetFileName(path)}.temp");
 
using (var readStream = sqlDataReader.GetStream(2))
{
using (var fileStream = new FileStream(temp, FileMode.Create,
FileAccess.Write,
FileShare.None))
using (var readStream = sqlDataReader.GetStream(2))
{
using (var zipStream =
new GZipStream(readStream, CompressionMode.Decompress))
using (var fileStream = new FileStream(temp, FileMode.Create,
FileAccess.Write,
FileShare.None))
{
zipStream.CopyTo(fileStream);
using (var zipStream =
new GZipStream(readStream, CompressionMode.Decompress))
{
zipStream.CopyTo(fileStream);
}
}
}
}
 
try
{
File.Replace(temp, path, null, true);
}
catch
{
try
{
File.Delete(temp);
File.Replace(temp, path, null, true);
}
catch (Exception exception)
catch
{
// Suppress deletion errors of temporary file.
Log.Warning(exception, "Could not delete temporary file.", temp);
try
{
File.Delete(temp);
}
catch (Exception exception)
{
// Suppress deletion errors of temporary file.
Log.Warning(exception, "Could not delete temporary file.", temp);
}
 
throw;
}
 
throw;
}
 
break;
default:
// Asynchronous
using (var readStream = sqlDataReader.GetStream(2))
{
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Create,
FileAccess.Write,
FileShare.Write,
cancellationToken))
break;
default:
// Asynchronous
using (var readStream = sqlDataReader.GetStream(2))
{
readStream.Position = 0L;
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Create,
FileAccess.Write,
FileShare.Write,
cancellationToken))
{
readStream.Position = 0L;
 
using (var zipStream =
new GZipStream(readStream, CompressionMode.Decompress))
{
await zipStream.CopyToAsync(fileStream);
using (var zipStream =
new GZipStream(readStream, CompressionMode.Decompress))
{
await zipStream.CopyToAsync(fileStream);
}
}
}
}
 
 
break;
break;
}
 
SnapshotRevert?.Invoke(this, new SnapshotRevertSuccessEventArgs(name));
}
 
SnapshotRevert?.Invoke(this, new SnapshotRevertSuccessEventArgs(name));
}
}
}
catch
{
SnapshotRevert?.Invoke(this, new SnapshotRevertFailureEventArgs(name));
catch
{
SnapshotRevert?.Invoke(this, new SnapshotRevertFailureEventArgs(name));
 
throw;
throw;
}
}
finally
{
_snapshotSemaphore.Release();
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task RemoveFileFast(IEnumerable<string> hashes, CancellationToken cancellationToken)
@@ -851,47 +908,55 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
try
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
var transactionCommands = new List<Task>();
try
{
var transactionCommands = new List<Task>();
 
foreach (var hash in hashes)
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveSnapshotFromHashSql, sqliteConnection, dbTransaction))
foreach (var hash in hashes)
{
sqliteCommand.Parameters.AddRange(new[]
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveSnapshotFromHashSql, sqliteConnection, dbTransaction))
{
new SQLiteParameter("@hash", hash)
});
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
 
sqliteCommand.Prepare();
sqliteCommand.Prepare();
 
var command = sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
var command = sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
transactionCommands.Add(command);
transactionCommands.Add(command);
}
}
}
 
await Task.WhenAll(transactionCommands);
await Task.WhenAll(transactionCommands);
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
throw;
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task RemoveFile(string hash, CancellationToken cancellationToken)
@@ -900,18 +965,18 @@
{
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveSnapshotFromHashSql, sqliteConnection, dbTransaction))
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
try
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveSnapshotFromHashSql, sqliteConnection, dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
{
@@ -920,19 +985,26 @@
 
sqliteCommand.Prepare();
 
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
throw;
}
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task UpdateColor(string hash, Color color, CancellationToken cancellationToken)
@@ -942,17 +1014,19 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
 
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateColorFromHashSql, sqliteConnection, dbTransaction))
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
try
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateColorFromHashSql, sqliteConnection, dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
{
@@ -962,19 +1036,26 @@
 
sqliteCommand.Prepare();
 
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
throw;
}
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task RemoveColor(string hash, CancellationToken cancellationToken)
@@ -984,17 +1065,18 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveColorFromHashSql, sqliteConnection, dbTransaction))
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
try
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveColorFromHashSql, sqliteConnection, dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
{
@@ -1003,19 +1085,26 @@
 
sqliteCommand.Prepare();
 
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
throw;
}
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task<SnapshotPreview> RetrievePreview(string hash, CancellationToken cancellationToken)
@@ -1025,118 +1114,64 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(RetrievePreviewFromHashSql, sqliteConnection))
{
sqliteCommand.Parameters.AddRange(new[]
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(RetrievePreviewFromHashSql, sqliteConnection))
{
new SQLiteParameter("@hash", hash)
});
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
 
var note = string.Empty;
var note = string.Empty;
 
sqliteCommand.Prepare();
sqliteCommand.Prepare();
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
if (!(sqlDataReader["Note"] is DBNull))
while (await sqlDataReader.ReadAsync(cancellationToken))
{
note = (string)sqlDataReader["Note"];
}
 
Bitmap shot = null;
 
if (!(sqlDataReader["Shot"] is DBNull))
{
var readStream = sqlDataReader.GetStream(2);
 
readStream.Position = 0L;
 
using (var zipStream = new GZipStream(readStream, CompressionMode.Decompress))
if (!(sqlDataReader["Note"] is DBNull))
{
using (var image = Image.FromStream(zipStream))
{
shot = new Bitmap(image);
}
note = (string)sqlDataReader["Note"];
}
}
 
return new SnapshotPreview(hash, shot, note);
}
Bitmap shot = null;
 
return null;
}
}
}
}
 
/*
public MemoryStream RetrieveFileStream(string hash, CancellationToken cancellationToken)
{
var connectionString = new SQLiteConnectionStringBuilder
{
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
 
sqliteConnection.Open();
 
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(RetrieveDataFromHashSql, sqliteConnection))
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
 
sqliteCommand.Prepare();
 
using (var sqlDataReader = sqliteCommand.ExecuteReader())
{
 
while (sqlDataReader.Read())
{
using (var readStream = sqlDataReader.GetStream(1))
{
 
using (var memoryStream = new MemoryStream())
if (!(sqlDataReader["Shot"] is DBNull))
{
var readStream = sqlDataReader.GetStream(2);
 
readStream.Position = 0L;
 
readStream.CopyTo(memoryStream);
 
memoryStream.Position = 0L;
 
using (var zipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
using (var zipStream = new GZipStream(readStream, CompressionMode.Decompress))
{
 
var outputStream = new MemoryStream();
 
zipStream.CopyTo(outputStream);
 
outputStream.Position = 0L;
 
return outputStream;
using (var image = Image.FromStream(zipStream))
{
shot = new Bitmap(image);
}
}
}
 
return new SnapshotPreview(hash, shot, note);
}
 
return null;
}
 
return null;
}
}
}
finally
{
_databaseLock.Release();
}
}
*/
 
public async Task<MemoryStream> RetrieveFileStream(string hash, CancellationToken cancellationToken)
{
@@ -1145,53 +1180,61 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(RetrieveDataFromHashSql, sqliteConnection))
{
sqliteCommand.Parameters.AddRange(new[]
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(RetrieveDataFromHashSql, sqliteConnection))
{
new SQLiteParameter("@hash", hash)
});
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
 
sqliteCommand.Prepare();
sqliteCommand.Prepare();
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
using (var readStream = sqlDataReader.GetStream(1))
while (await sqlDataReader.ReadAsync(cancellationToken))
{
using (var memoryStream = new MemoryStream())
using (var readStream = sqlDataReader.GetStream(1))
{
readStream.Position = 0L;
using (var memoryStream = new MemoryStream())
{
readStream.Position = 0L;
 
await readStream.CopyToAsync(memoryStream);
await readStream.CopyToAsync(memoryStream);
 
memoryStream.Position = 0L;
memoryStream.Position = 0L;
 
using (var zipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
{
// Do not dispose the returned stream and leave it up to callers to dispose.
var outputStream = new MemoryStream();
using (var zipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
{
// Do not dispose the returned stream and leave it up to callers to dispose.
var outputStream = new MemoryStream();
 
await zipStream.CopyToAsync(outputStream);
await zipStream.CopyToAsync(outputStream);
 
outputStream.Position = 0L;
outputStream.Position = 0L;
 
return outputStream;
return outputStream;
}
}
}
}
 
return null;
}
 
return null;
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task RelocateFile(string hash, string path, CancellationToken cancellationToken)
@@ -1201,17 +1244,18 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RelocateFileFromHashSql, sqliteConnection, dbTransaction))
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
try
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RelocateFileFromHashSql, sqliteConnection, dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
{
@@ -1221,19 +1265,27 @@
 
sqliteCommand.Prepare();
 
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
throw;
}
}
}
}
}
 
finally
{
_databaseLock.Release();
}
}
 
public async Task UpdateNote(string hash, string note, CancellationToken cancellationToken)
@@ -1243,17 +1295,18 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateNoteFromHashSql, sqliteConnection, dbTransaction))
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
try
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateNoteFromHashSql, sqliteConnection, dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
{
@@ -1263,137 +1316,155 @@
 
sqliteCommand.Prepare();
 
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
dbTransaction.Commit();
 
SnapshotNoteUpdate?.Invoke(this, new SnapshotNoteUpdateSuccessEventArgs(note));
}
catch
{
dbTransaction.Rollback();
SnapshotNoteUpdate?.Invoke(this, new SnapshotNoteUpdateSuccessEventArgs(note));
}
catch
{
dbTransaction.Rollback();
 
SnapshotNoteUpdate?.Invoke(this, new SnapshotNoteUpdateFailureEventArgs());
SnapshotNoteUpdate?.Invoke(this, new SnapshotNoteUpdateFailureEventArgs());
 
throw;
throw;
}
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task<string> UpdateFile(string hash, byte[] data, CancellationToken cancellationToken)
{
using (var dataMemoryStream = new MemoryStream(data))
var connectionString = new SQLiteConnectionStringBuilder
{
var connectionString = new SQLiteConnectionStringBuilder
{
ConnectionString = DatabaseConnectionString
};
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var dataMemoryStream = new MemoryStream(data))
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
try
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
using (var md5 = MD5.Create())
try
{
using (var hashMemoryStream = new MemoryStream())
using (var md5 = MD5.Create())
{
dataMemoryStream.Position = 0L;
await dataMemoryStream.CopyToAsync(hashMemoryStream);
using (var hashMemoryStream = new MemoryStream())
{
dataMemoryStream.Position = 0L;
await dataMemoryStream.CopyToAsync(hashMemoryStream);
 
hashMemoryStream.Position = 0L;
var recomputedHash = md5.ComputeHash(hashMemoryStream);
var hashHex = BitConverter.ToString(recomputedHash).Replace("-", "")
.ToLowerInvariant();
hashMemoryStream.Position = 0L;
var recomputedHash = md5.ComputeHash(hashMemoryStream);
var hashHex = BitConverter.ToString(recomputedHash).Replace("-", "")
.ToLowerInvariant();
 
using (var fileMemoryStream = new MemoryStream())
{
using (var fileZipStream =
new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
using (var fileMemoryStream = new MemoryStream())
{
dataMemoryStream.Position = 0L;
await dataMemoryStream.CopyToAsync(fileZipStream);
fileZipStream.Close();
using (var fileZipStream =
new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
{
dataMemoryStream.Position = 0L;
await dataMemoryStream.CopyToAsync(fileZipStream);
fileZipStream.Close();
 
fileMemoryStream.Position = 0L;
fileMemoryStream.Position = 0L;
 
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateFileSql, sqliteConnection, dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateFileSql, sqliteConnection,
dbTransaction))
{
new SQLiteParameter("@dataLength", fileMemoryStream.Length),
new SQLiteParameter("@recomputedHash", hashHex),
new SQLiteParameter("@hash", hash)
});
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@dataLength", fileMemoryStream.Length),
new SQLiteParameter("@recomputedHash", hashHex),
new SQLiteParameter("@hash", hash)
});
 
sqliteCommand.Prepare();
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
}
sqliteCommand.Prepare();
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
}
 
using (var sqliteCommand =
new SQLiteCommand(GetRowFromHashSql, sqliteConnection,
dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
using (var sqliteCommand =
new SQLiteCommand(GetRowFromHashSql, sqliteConnection,
dbTransaction))
{
new SQLiteParameter("@hash", hashHex)
});
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hashHex)
});
 
sqliteCommand.Prepare();
sqliteCommand.Prepare();
 
using (var sqlDataReader =
await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
using (var sqlDataReader =
await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
if (sqlDataReader["id"] is long rowId)
while (await sqlDataReader.ReadAsync(cancellationToken))
{
using (var sqliteBlob = SQLiteBlob.Create(sqliteConnection,
"main",
"Snapshots",
"Data",
rowId, false))
if (sqlDataReader["id"] is long rowId)
{
var fileMemoryStreamData = fileMemoryStream.ToArray();
using (var sqliteBlob = SQLiteBlob.Create(
sqliteConnection,
"main",
"Snapshots",
"Data",
rowId, false))
{
var fileMemoryStreamData =
fileMemoryStream.ToArray();
 
sqliteBlob.Write(fileMemoryStreamData,
fileMemoryStreamData.Length,
0);
sqliteBlob.Write(fileMemoryStreamData,
fileMemoryStreamData.Length,
0);
}
}
}
}
}
}
 
dbTransaction.Commit();
dbTransaction.Commit();
 
SnapshotDataUpdate?.Invoke(this,
new SnapshotDataUpdateSuccessEventArgs(hash, hashHex));
SnapshotDataUpdate?.Invoke(this,
new SnapshotDataUpdateSuccessEventArgs(hash, hashHex));
 
return hashHex;
return hashHex;
}
}
}
}
}
}
catch
{
dbTransaction.Rollback();
catch
{
dbTransaction.Rollback();
 
SnapshotDataUpdate?.Invoke(this, new SnapshotDataUpdateFailureEventArgs(hash));
SnapshotDataUpdate?.Invoke(this, new SnapshotDataUpdateFailureEventArgs(hash));
 
throw;
throw;
}
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task UpdateHash(string from, string to, CancellationToken cancellationToken)
@@ -1403,17 +1474,18 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateHashFromHashSql, sqliteConnection, dbTransaction))
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
try
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateHashFromHashSql, sqliteConnection, dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
{
@@ -1423,19 +1495,26 @@
 
sqliteCommand.Prepare();
 
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
throw;
}
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
#endregion
@@ -1442,7 +1521,7 @@
 
#region Private Methods
 
private static async Task SetAutoVacuum(CancellationToken cancellationToken)
private async Task SetAutoVacuum(CancellationToken cancellationToken)
{
var connectionString = new SQLiteConnectionStringBuilder
{
@@ -1449,20 +1528,28 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Set auto vacuum.
using (var sqliteCommand = new SQLiteCommand(SetAutoVacuumSql, sqliteConnection))
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
// Set auto vacuum.
using (var sqliteCommand = new SQLiteCommand(SetAutoVacuumSql, sqliteConnection))
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
}
}
}
finally
{
_databaseLock.Release();
}
}
 
private static async Task CreateDatabase(CancellationToken cancellationToken)
private async Task CreateDatabase(CancellationToken cancellationToken)
{
var connectionString = new SQLiteConnectionStringBuilder
{
@@ -1469,30 +1556,38 @@
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await _databaseLock.WaitAsync(cancellationToken);
try
{
await sqliteConnection.OpenAsync(cancellationToken);
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
// Create the table if it does not exist.
using (var sqliteCommand = new SQLiteCommand(CreateTableSql, sqliteConnection, dbTransaction))
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
try
// Create the table if it does not exist.
using (var sqliteCommand = new SQLiteCommand(CreateTableSql, sqliteConnection, dbTransaction))
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
throw;
}
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
#endregion
/Horizon/Horizon.csproj
@@ -41,7 +41,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>7.3</LangVersion>
<LangVersion>8</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -51,7 +51,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>7.3</LangVersion>
<LangVersion>8</LangVersion>
</PropertyGroup>
<PropertyGroup />
<PropertyGroup>
@@ -67,9 +67,12 @@
<Reference Include="BouncyCastle.Crypto, Version=1.9.0.0, Culture=neutral, PublicKeyToken=0e99375e54769942, processorArchitecture=MSIL">
<HintPath>..\packages\NetSparkleUpdater.UI.WinForms.NetFramework.2.2.3\lib\net452\BouncyCastle.Crypto.dll</HintPath>
</Reference>
<Reference Include="Jot, Version=2.1.14.0, Culture=neutral, PublicKeyToken=6b498f69c5f88322, processorArchitecture=MSIL">
<HintPath>..\packages\Jot.2.1.16\lib\netstandard2.0\Jot.dll</HintPath>
<Reference Include="Jot, Version=2.1.17.0, Culture=neutral, PublicKeyToken=6b498f69c5f88322, processorArchitecture=MSIL">
<HintPath>..\packages\Jot.2.1.17\lib\netstandard2.0\Jot.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=7.0.0.1, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.Logging.Abstractions.7.0.1\lib\net462\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
</Reference>
@@ -164,6 +167,12 @@
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.Threading.Tasks.Dataflow, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Dataflow.7.0.0\lib\net462\System.Threading.Tasks.Dataflow.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xaml" />
<Reference Include="System.Xml" />
@@ -185,6 +194,9 @@
<Compile Include="Database\SnapshotDataUpdateFailureEventArgs.cs" />
<Compile Include="Database\SnapshotNoteUpdateEventArgs.cs" />
<Compile Include="Database\SnapshotNoteUpdateFailureEventArgs.cs" />
<Compile Include="Snapshots\CreateSnapshotProgress.cs" />
<Compile Include="Snapshots\CreateSnapshotProgressFailure.cs" />
<Compile Include="Snapshots\CreateSnapshotProgressSuccess.cs" />
<Compile Include="Utilities\LogMemorySink.cs" />
<Compile Include="LogViewForm.cs">
<SubType>Form</SubType>
/Horizon/MainForm.cs
@@ -10,7 +10,9 @@
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
using System.Windows.Shapes;
using Horizon.Database;
using Horizon.Snapshots;
using Horizon.Utilities;
@@ -22,6 +24,7 @@
using Serilog;
using TrackedFolders;
using CaptureMode = Configuration.CaptureMode;
using Path = System.IO.Path;
 
namespace Horizon
{
@@ -88,7 +91,7 @@
rollingInterval: RollingInterval.Day)
.CreateLogger();
 
_snapshotDatabase = new SnapshotDatabase();
_snapshotDatabase = new SnapshotDatabase(_cancellationToken);
_snapshotDatabase.SnapshotRevert += SnapshotDatabase_SnapshotRevert;
_snapshotDatabase.SnapshotCreate += SnapshotDatabase_SnapshotCreate;
 
@@ -408,13 +411,13 @@
 
if (_changedFiles.Contains(e.FullPath))
{
_changedFilesContinuation.Schedule(delay, () => TakeSnapshots(color), _cancellationToken);
_changedFilesContinuation.Schedule(delay, async () => await TakeSnapshots(color, _cancellationToken), _cancellationToken);
return;
}
 
_changedFiles.Add(e.FullPath);
 
_changedFilesContinuation.Schedule(delay, () => TakeSnapshots(color), _cancellationToken);
_changedFilesContinuation.Schedule(delay, async () => await TakeSnapshots(color, _cancellationToken), _cancellationToken);
}
catch (Exception exception)
{
@@ -732,51 +735,63 @@
}
}
 
private async void TakeSnapshots(Color color)
private async Task TakeSnapshots(Color color, CancellationToken cancellationToken)
{
await _changedFilesLock.WaitAsync(_cancellationToken);
try
var bufferBlock = new BufferBlock<string>(new DataflowBlockOptions() {CancellationToken = cancellationToken});
var actionBlock = new ActionBlock<string>(async path =>
{
foreach (var path in _changedFiles)
// In case files have vanished strictly due to the time specified by the tracked folders delay.
if (!File.Exists(path))
{
// In case files have vanished strictly due to the time specified by the tracked folders delay.
if (!File.Exists(path))
{
Log.Warning("File vanished after tracked folder delay.", path);
Log.Warning("File vanished after tracked folder delay.", path);
 
continue;
}
return;
}
 
try
{
var fileName = Path.GetFileName(path);
var screenCapture = ScreenCapture.Capture((Utilities.CaptureMode)Configuration.CaptureMode);
try
{
var fileName = System.IO.Path.GetFileName(path);
var screenCapture = ScreenCapture.Capture((Utilities.CaptureMode)Configuration.CaptureMode);
 
await _snapshotDatabase.CreateSnapshot(fileName, path, screenCapture, color,
_cancellationToken);
}
catch (SQLiteException exception)
await _snapshotDatabase.CreateSnapshot(fileName, path, screenCapture, color,
_cancellationToken);
}
catch (SQLiteException exception)
{
if (exception.ResultCode == SQLiteErrorCode.Constraint)
{
if (exception.ResultCode == SQLiteErrorCode.Constraint)
{
Log.Information(exception, "Snapshot already exists.");
}
Log.Information(exception, "Snapshot already exists.");
}
catch (Exception exception)
}
catch (Exception exception)
{
Log.Error(exception, "Could not take snapshot.", path);
}
});
 
using (var snapshotLink =
bufferBlock.LinkTo(actionBlock, new DataflowLinkOptions() { PropagateCompletion = true }))
{
await _changedFilesLock.WaitAsync(_cancellationToken);
try
{
foreach (var path in _changedFiles)
{
Log.Error(exception, "Could not take snapshot.", path);
await bufferBlock.SendAsync(path, cancellationToken);
}
bufferBlock.Complete();
await bufferBlock.Completion;
}
catch (Exception exception)
{
Log.Error(exception, "Could not take snapshots.");
}
finally
{
_changedFiles.Clear();
_changedFilesLock.Release();
}
}
catch (Exception exception)
{
Log.Error(exception, "Could not take snapshots.");
}
finally
{
_changedFiles.Clear();
_changedFilesLock.Release();
}
}
 
#endregion
/Horizon/Snapshots/CreateSnapshotProgress.cs
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Horizon.Snapshots
{
public class CreateSnapshotProgress
{
#region Constructors, Destructors and Finalizers
 
public CreateSnapshotProgress(string file, bool success)
{
Success = success;
File = file;
}
 
#endregion
 
#region Public Enums, Properties and Fields
 
public bool Success { get; }
 
public string File { get; }
 
#endregion
}
}
/Horizon/Snapshots/CreateSnapshotProgressFailure.cs
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Horizon.Snapshots
{
public class CreateSnapshotProgressFailure : CreateSnapshotProgress
{
public CreateSnapshotProgressFailure(string file, Exception exception) : base(file,false)
{
Exception = exception;
}
 
public Exception Exception { get; }
}
}
/Horizon/Snapshots/CreateSnapshotProgressSuccess.cs
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Horizon.Snapshots
{
public class CreateSnapshotProgressSuccess : CreateSnapshotProgress
{
public CreateSnapshotProgressSuccess(string file) : base(file, true)
{
}
}
}
/Horizon/Snapshots/SnapshotManagerForm.cs
@@ -14,6 +14,7 @@
using Horizon.Database;
using Horizon.Utilities;
using Microsoft.WindowsAPICodePack.Dialogs;
using Org.BouncyCastle.Crypto;
using Serilog;
 
namespace Horizon.Snapshots
@@ -58,7 +59,7 @@
 
#region Constructors, Destructors and Finalizers
 
public SnapshotManagerForm()
private SnapshotManagerForm()
{
InitializeComponent();
Utilities.WindowState.FormTracker.Track(this);
@@ -592,21 +593,21 @@
toolStripProgressBar1.Maximum = (int)await _snapshotDatabase.CountSnapshots(_cancellationToken);
 
var snapshotQueue = new ConcurrentQueue<Snapshot>();
var snapshotsQueuedTaskCompletionSource = new TaskCompletionSource<object>();
 
async void IdleHandler(object idleHandlerSender, EventArgs idleHandlerArgs)
void IdleHandler(object idleHandlerSender, EventArgs idleHandlerArgs)
{
await snapshotsQueuedTaskCompletionSource.Task;
 
try
{
if (!snapshotQueue.TryDequeue(out var snapshot))
if (snapshotQueue.IsEmpty)
{
Application.Idle -= IdleHandler;
 
dataGridView1.Sort(dataGridView1.Columns["TimeColumn"], ListSortDirection.Descending);
toolStripStatusLabel1.Text = "Done.";
}
 
if (!snapshotQueue.TryDequeue(out var snapshot))
{
return;
}
 
@@ -629,16 +630,15 @@
Log.Error(exception, "Could not update data grid view.");
}
}
 
Application.Idle += IdleHandler;
try
{
foreach (var snapshot in await _snapshotDatabase.LoadSnapshots(_cancellationToken))
await foreach (var snapshot in _snapshotDatabase.LoadSnapshots(_cancellationToken).WithCancellation(_cancellationToken))
{
snapshotQueue.Enqueue(snapshot);
}
 
snapshotsQueuedTaskCompletionSource.TrySetResult(new { });
Application.Idle += IdleHandler;
}
catch (Exception exception)
{
@@ -676,6 +676,56 @@
e.Effect = DragDropEffects.None;
}
 
private async Task CreateSnapshots(IReadOnlyList<string> files, Bitmap screenCapture, TrackedFolders.TrackedFolders trackedFolders, IProgress<CreateSnapshotProgress> progress, CancellationToken cancellationToken)
{
Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = 512 }, async file =>
{
var color = Color.Empty;
if (_mainForm.TrackedFolders.TryGet(file, out var folder))
{
color = folder.Color;
}
 
var fileInfo = File.GetAttributes(file);
if (fileInfo.HasFlag(FileAttributes.Directory))
{
foreach (var directoryFile in Directory.GetFiles(file, "*.*", SearchOption.AllDirectories))
{
var name = Path.GetFileName(directoryFile);
var path = Path.Combine(Path.GetDirectoryName(directoryFile), name);
 
try
{
await _snapshotDatabase.CreateSnapshot(name, path, screenCapture, color,
_cancellationToken);
 
progress.Report(new CreateSnapshotProgressSuccess(file));
}
catch (Exception exception)
{
progress.Report(new CreateSnapshotProgressFailure(file, exception));
}
}
 
return;
}
 
var fileName = Path.GetFileName(file);
var pathName = Path.Combine(Path.GetDirectoryName(file), fileName);
 
try
{
await _snapshotDatabase.CreateSnapshot(fileName, pathName, screenCapture, color,
_cancellationToken);
 
progress.Report(new CreateSnapshotProgressSuccess(file));
}
catch (Exception exception)
{
progress.Report(new CreateSnapshotProgressFailure(file, exception));
}
});
}
private async void DataGridView1_DragDrop(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
@@ -690,17 +740,49 @@
toolStripStatusLabel1.Text = "Snapshotting files...";
 
var screenCapture = ScreenCapture.Capture((CaptureMode)_mainForm.Configuration.CaptureMode);
for (var i = 0; i < files.Length; ++i)
 
var progress = new Progress<CreateSnapshotProgress>(createSnapshotProgress =>
{
switch (createSnapshotProgress)
{
case CreateSnapshotProgressSuccess createSnapshotProgressSuccess:
toolStripStatusLabel1.Text = $"Snapshot taken of {createSnapshotProgressSuccess.File}.";
break;
case CreateSnapshotProgressFailure createSnapshotProgressFailure:
if (createSnapshotProgressFailure.Exception is SQLiteException { ResultCode: SQLiteErrorCode.Constraint })
{
toolStripStatusLabel1.Text = $"Snapshot of file {createSnapshotProgressFailure.File} already exists.";
break;
}
 
toolStripStatusLabel1.Text = $"Could not snapshot file {createSnapshotProgressFailure.File}";
Log.Warning(createSnapshotProgressFailure.Exception, $"Could not snapshot file {createSnapshotProgressFailure.File}");
break;
}
 
toolStripProgressBar1.Increment(1);
statusStrip1.Update();
});
 
await Task.Factory.StartNew(async () =>
{
await CreateSnapshots(files, screenCapture, _mainForm.TrackedFolders, progress, _cancellationToken);
}, _cancellationToken);
 
/*
 
foreach (var file in files)
{
var color = Color.Empty;
if (_mainForm.TrackedFolders.TryGet(files[i], out var folder))
if (_mainForm.TrackedFolders.TryGet(file, out var folder))
{
color = folder.Color;
}
 
if (Directory.Exists(files[i]))
var fileInfo = File.GetAttributes(file);
if (fileInfo.HasFlag(FileAttributes.Directory))
{
foreach (var directoryFile in Directory.GetFiles(files[i], "*.*", SearchOption.AllDirectories))
foreach (var directoryFile in Directory.GetFiles(file, "*.*", SearchOption.AllDirectories))
{
var name = Path.GetFileName(directoryFile);
var path = Path.Combine(Path.GetDirectoryName(directoryFile), name);
@@ -710,8 +792,9 @@
await _snapshotDatabase.CreateSnapshot(name, path, screenCapture, color,
_cancellationToken);
 
toolStripProgressBar1.Value = i + 1;
toolStripStatusLabel1.Text = $"Snapshot taken of {files[i]}.";
toolStripStatusLabel1.Text = $"Snapshot taken of {file}.";
toolStripProgressBar1.Increment(1);
statusStrip1.Update();
}
catch (SQLiteException exception)
@@ -719,9 +802,9 @@
if (exception.ResultCode == SQLiteErrorCode.Constraint)
{
Log.Information(exception, "Snapshot already exists.");
 
toolStripProgressBar1.Value = i + 1;
toolStripStatusLabel1.Text = "Snapshot already exists.";
toolStripProgressBar1.Increment(1);
statusStrip1.Update();
}
}
@@ -734,16 +817,16 @@
continue;
}
 
var fileName = Path.GetFileName(files[i]);
var pathName = Path.Combine(Path.GetDirectoryName(files[i]), fileName);
var fileName = Path.GetFileName(file);
var pathName = Path.Combine(Path.GetDirectoryName(file), fileName);
 
try
{
await _snapshotDatabase.CreateSnapshot(fileName, pathName, screenCapture, color,
_cancellationToken);
 
toolStripProgressBar1.Value = i + 1;
toolStripStatusLabel1.Text = $"Snapshot taken of {files[i]}.";
toolStripStatusLabel1.Text = $"Snapshot taken of {file}.";
toolStripProgressBar1.Increment(1);
statusStrip1.Update();
}
catch (SQLiteException exception)
@@ -751,9 +834,9 @@
if (exception.ResultCode == SQLiteErrorCode.Constraint)
{
Log.Information(exception, "Snapshot already exists.");
 
toolStripProgressBar1.Value = i + 1;
toolStripStatusLabel1.Text = "Snapshot already exists.";
toolStripProgressBar1.Increment(1);
statusStrip1.Update();
}
}
@@ -762,6 +845,7 @@
Log.Warning(exception, "Could not create snapshot");
}
}
*/
}
 
private async void FileToolStripMenuItem_Click(object sender, EventArgs e)
/Horizon/packages.config
@@ -2,7 +2,8 @@
<packages>
<package id="Be.Windows.Forms.HexBox" version="1.6.1" targetFramework="net452" />
<package id="EntityFramework" version="6.4.4" targetFramework="net472" />
<package id="Jot" version="2.1.16" targetFramework="net472" />
<package id="Jot" version="2.1.17" targetFramework="net472" />
<package id="Microsoft.Bcl.AsyncInterfaces" version="7.0.0" targetFramework="net472" />
<package id="Microsoft.Extensions.Logging.Abstractions" version="7.0.1" targetFramework="net472" />
<package id="Microsoft.WindowsAPICodePack-Core" version="1.1.0.2" targetFramework="net472" />
<package id="Microsoft.WindowsAPICodePack-Shell" version="1.1.0.0" targetFramework="net472" />
@@ -28,6 +29,8 @@
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net472" />
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net472" />
<package id="System.Security.Cryptography.X509Certificates" version="4.3.2" targetFramework="net472" />
<package id="System.Threading.Tasks.Dataflow" version="7.0.0" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
<package id="VirtualFileDataObject" version="1.0.0.0" targetFramework="net472" />
<package id="WindowsAPICodePack-Core" version="1.1.2" targetFramework="net472" />
<package id="WindowsAPICodePack-Shell" version="1.1.1" targetFramework="net472" />
/TrackedFolders/TrackedFolders.cs
@@ -15,6 +15,16 @@
 
#endregion
 
#region Constructors
 
[UsedImplicitly]
public TrackedFolders()
{
 
}
 
#endregion
 
#region Public Enums, Properties and Fields
 
public BindingListWithCollectionChanged<Folder> Folder
/TrackedFolders/TrackedFolders.csproj
@@ -22,7 +22,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>7.3</LangVersion>
<LangVersion>8</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -31,7 +31,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>7.3</LangVersion>
<LangVersion>8</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />