Horizon

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ HEAD  →  ?path2? @ 1
/Horizon/Database/SnapshotDatabase.cs
@@ -6,7 +6,6 @@
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;
@@ -97,33 +96,22 @@
#region Private Delegates, Events, Enums, Properties, Indexers and Fields
 
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly SemaphoreSlim _databaseLock;
private readonly SQLiteConnectionStringBuilder _sqliteConnectionStringBuilder;
 
private SemaphoreSlim _snapshotSemaphore;
 
#endregion
 
#region Constructors, Destructors and Finalizers
 
private SnapshotDatabase()
public SnapshotDatabase()
{
Directory.CreateDirectory(Constants.DatabaseDirectory);
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
 
_databaseLock = new SemaphoreSlim(1, 1);
_snapshotSemaphore = new SemaphoreSlim(1, 1);
 
_sqliteConnectionStringBuilder = new SQLiteConnectionStringBuilder
{
ConnectionString = DatabaseConnectionString
};
}
Directory.CreateDirectory(Constants.DatabaseDirectory);
 
public SnapshotDatabase(CancellationToken cancellationToken) : this()
{
_cancellationTokenSource = new CancellationTokenSource();
var localCancellationToken = _cancellationTokenSource.Token;
var combinedCancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(localCancellationToken, cancellationToken);
_cancellationToken = combinedCancellationTokenSource.Token;
 
CreateDatabase(_cancellationToken).ContinueWith(async createDatabaseTask =>
{
try
@@ -134,14 +122,14 @@
{
await SetAutoVacuum(_cancellationToken);
}
catch (Exception exception)
catch
{
Log.Error(exception, "Unable to set auto vacuum for database.");
Log.Error("Unable to set auto vacuum for database.");
}
}
catch (Exception exception)
catch
{
Log.Error(exception, "Unable to create database;");
Log.Error("Unable to create database;");
}
}).Wait(_cancellationToken);
}
@@ -149,6 +137,9 @@
public void Dispose()
{
_cancellationTokenSource.Cancel();
 
_snapshotSemaphore?.Dispose();
_snapshotSemaphore = null;
}
 
#endregion
@@ -155,7 +146,7 @@
 
#region Public Methods
 
public async Task DeleteScreenshotAsync(string hash, CancellationToken cancellationToken)
public async Task DeleteScreenshot(string hash, CancellationToken cancellationToken)
{
var connectionString = new SQLiteConnectionStringBuilder
{
@@ -162,18 +153,17 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveScreenshotFromHashSql, sqliteConnection, dbTransaction))
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveScreenshotFromHashSql, sqliteConnection, dbTransaction))
try
{
sqliteCommand.Parameters.AddRange(new[]
{
@@ -182,479 +172,464 @@
 
sqliteCommand.Prepare();
 
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
}
throw;
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task NormalizeTimeAsync(string hash, CancellationToken cancellationToken)
public async Task NormalizeTime(string hash, CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection =
new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var readSQLiteCommand = new SQLiteCommand(RetrieveTimeFromHash, sqliteConnection))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var readSQLiteCommand = new SQLiteCommand(RetrieveTimeFromHash, sqliteConnection))
readSQLiteCommand.Parameters.AddRange(new[]
{
readSQLiteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
new SQLiteParameter("@hash", hash)
});
 
readSQLiteCommand.Prepare();
readSQLiteCommand.Prepare();
 
using (var sqlDataReader = await readSQLiteCommand.ExecuteReaderAsync(cancellationToken))
using (var sqlDataReader = await readSQLiteCommand.ExecuteReaderAsync(cancellationToken))
{
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
using (var dbTransaction = sqliteConnection.BeginTransaction())
try
{
try
while (await sqlDataReader.ReadAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
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 _))
{
var time = (string)sqlDataReader["Time"];
continue;
}
 
// Skip if already ISO 8601
if (DateTime.TryParseExact(time,
"yyyy-MM-ddTHH:mm:ss.fff",
CultureInfo.InvariantCulture,
DateTimeStyles.None, out _))
{
continue;
}
if (!DateTime.TryParse(time, out var dateTime))
{
dateTime = DateTime.Now;
}
 
if (!DateTime.TryParse(time, out var dateTime))
using (var writeSQLiteCommand =
new SQLiteCommand(UpdateTimeFromHash, sqliteConnection, dbTransaction))
{
writeSQLiteCommand.Parameters.AddRange(new[]
{
dateTime = DateTime.Now;
}
new SQLiteParameter("@time", dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fff")),
new SQLiteParameter("@hash", hash)
});
 
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)
});
writeSQLiteCommand.Prepare();
 
writeSQLiteCommand.Prepare();
 
await writeSQLiteCommand.ExecuteNonQueryAsync(cancellationToken);
}
await writeSQLiteCommand.ExecuteNonQueryAsync(cancellationToken);
}
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
}
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
}
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task<long> CountSnapshotsAsync(CancellationToken cancellationToken)
public async Task<long> CountSnapshots(CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(CountSnapshotsSql, sqliteConnection))
{
await sqliteConnection.OpenAsync(cancellationToken);
long count = 0;
 
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(CountSnapshotsSql, sqliteConnection))
sqliteCommand.Prepare();
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
long count = 0;
 
sqliteCommand.Prepare();
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
while (await sqlDataReader.ReadAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
if (!(sqlDataReader[0] is long dbCount))
{
if (!(sqlDataReader[0] is long dbCount))
{
count = -1;
break;
}
 
count = dbCount;
count = -1;
break;
}
 
return count;
count = dbCount;
}
 
return count;
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async IAsyncEnumerable<Snapshot> LoadSnapshotsAsync([EnumeratorCancellation] CancellationToken cancellationToken)
public async Task<IEnumerable<Snapshot>> LoadSnapshots(CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection =
new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(RetrieveSnapshotsSql, sqliteConnection))
{
await sqliteConnection.OpenAsync(cancellationToken);
sqliteCommand.Prepare();
 
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(RetrieveSnapshotsSql, sqliteConnection))
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
sqliteCommand.Prepare();
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"];
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
//var snapshots = new List<Snapshot>();
while (await sqlDataReader.ReadAsync(cancellationToken))
var color = Color.Empty;
 
if (!(sqlDataReader["Color"] is DBNull))
{
var name = (string)sqlDataReader["Name"];
var path = (string)sqlDataReader["Path"];
var time = (string)sqlDataReader["Time"];
var hash = (string)sqlDataReader["Hash"];
var dbColor = Convert.ToInt32(sqlDataReader["Color"]);
 
var color = Color.Empty;
 
if (!(sqlDataReader["Color"] is DBNull))
switch (dbColor)
{
var dbColor = Convert.ToInt32(sqlDataReader["Color"]);
 
switch (dbColor)
{
case 0:
color = Color.Empty;
break;
default:
color = Color.FromArgb(dbColor);
break;
}
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 CreateSnapshotAsync(string name, string path, Color color, CancellationToken cancellationToken)
public async Task CreateSnapshot(string name, string path, Color color, CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
await _snapshotSemaphore.WaitAsync(cancellationToken);
 
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection =
new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
try
{
try
using (var md5 = MD5.Create())
{
using (var md5 = MD5.Create())
using (var hashMemoryStream = new MemoryStream())
{
using (var hashMemoryStream = new MemoryStream())
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Open, FileAccess.Read,
FileShare.Read,
cancellationToken))
{
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Open, FileAccess.Read,
FileShare.Read,
cancellationToken))
{
fileStream.Position = 0L;
await fileStream.CopyToAsync(hashMemoryStream);
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 fileMemoryStream = new MemoryStream())
{
using (var fileZipStream =
new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
{
using (var fileZipStream =
new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
{
fileStream.Position = 0L;
await fileStream.CopyToAsync(fileZipStream);
fileZipStream.Close();
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))
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(SnapshotFileNoScreenshotSql, sqliteConnection,
dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
{
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)
});
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)
{
SnapshotCreate?.Invoke(this,
new SnapshotCreateFailureEventArgs(name, path, color, exception));
}
 
throw;
}
catch (Exception exception)
if (exception.ResultCode != SQLiteErrorCode.Constraint)
{
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 CreateSnapshotAsync(string name, string path,
public async Task CreateSnapshot(string name, string path,
Bitmap shot, Color color, CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
await _snapshotSemaphore.WaitAsync(cancellationToken);
 
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection =
new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
try
{
try
using (var md5 = MD5.Create())
{
using (var md5 = MD5.Create())
using (var hashMemoryStream = new MemoryStream())
{
using (var hashMemoryStream = new MemoryStream())
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Open, FileAccess.Read,
FileShare.Read,
cancellationToken))
{
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Open, FileAccess.Read,
FileShare.Read,
cancellationToken))
{
fileStream.Position = 0L;
await fileStream.CopyToAsync(hashMemoryStream);
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 fileMemoryStream = new MemoryStream())
{
using (var fileZipStream =
new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
{
using (var fileZipStream =
new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
fileStream.Position = 0L;
await fileStream.CopyToAsync(fileZipStream);
fileZipStream.Close();
 
using (var bitmapMemoryStream = new MemoryStream())
{
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 bitmapZipStream =
new GZipStream(bitmapMemoryStream, CompressionMode.Compress,
true))
{
shot.Save(bitmapZipStream, ImageFormat.Bmp);
bitmapZipStream.Close();
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))
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(SnapshotFileSql, sqliteConnection,
dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
{
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)
});
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();
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);
}
 
using (var sqliteBlob =
SQLiteBlob.Create(sqliteConnection, "main",
"Snapshots",
"Shot",
rowId,
false))
{
var bitmapMemoryStreamData =
bitmapMemoryStream.ToArray();
using (var sqliteBlob =
SQLiteBlob.Create(sqliteConnection, "main", "Snapshots",
"Shot",
rowId,
false))
{
var bitmapMemoryStreamData = bitmapMemoryStream.ToArray();
 
sqliteBlob.Write(bitmapMemoryStreamData,
bitmapMemoryStreamData.Length,
0);
}
sqliteBlob.Write(bitmapMemoryStreamData,
bitmapMemoryStreamData.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));
}
}
}
@@ -662,81 +637,81 @@
}
}
}
catch (SQLiteException exception)
{
dbTransaction.Rollback();
}
catch (SQLiteException exception)
{
dbTransaction.Rollback();
 
if (exception.ResultCode != SQLiteErrorCode.Constraint)
{
SnapshotCreate?.Invoke(this,
new SnapshotCreateFailureEventArgs(name, path, color, exception));
}
 
throw;
}
catch (Exception exception)
if (exception.ResultCode != SQLiteErrorCode.Constraint)
{
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 SaveFileAsync(string path, string hash, CancellationToken cancellationToken)
public async Task SaveFile(string path, string hash, CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RetrieveDataPathFromHashSql, sqliteConnection))
{
await sqliteConnection.OpenAsync(cancellationToken);
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
 
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RetrieveDataPathFromHashSql, sqliteConnection))
sqliteCommand.Prepare();
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
sqliteCommand.Parameters.AddRange(new[]
while (await sqlDataReader.ReadAsync(cancellationToken))
{
new SQLiteParameter("@hash", hash)
});
// Create directories if they do not exist.
var dir = Path.GetDirectoryName(path);
 
sqliteCommand.Prepare();
if (dir != null && !Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
using (var readStream = sqlDataReader.GetStream(2))
{
// Create directories if they do not exist.
var dir = Path.GetDirectoryName(path);
 
if (dir != null && !Directory.Exists(dir))
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Create, FileAccess.Write,
FileShare.Write,
cancellationToken))
{
Directory.CreateDirectory(dir);
}
readStream.Position = 0L;
 
using (var readStream = sqlDataReader.GetStream(2))
{
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Create, FileAccess.Write,
FileShare.Write,
cancellationToken))
using (var zipStream = new GZipStream(readStream, CompressionMode.Decompress))
{
readStream.Position = 0L;
 
using (var zipStream = new GZipStream(readStream, CompressionMode.Decompress))
{
await zipStream.CopyToAsync(fileStream);
}
await zipStream.CopyToAsync(fileStream);
}
}
}
@@ -744,168 +719,209 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task RevertFileAsync(string name, string hash, CancellationToken cancellationToken, bool atomic = true)
public async Task RevertFile(string name, string hash, CancellationToken cancellationToken, bool atomic = true)
{
await _databaseLock.WaitAsync(cancellationToken);
try
await _snapshotSemaphore.WaitAsync(cancellationToken);
 
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RetrieveDataPathFromHashSql, sqliteConnection))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RetrieveDataPathFromHashSql, sqliteConnection))
try
{
try
sqliteCommand.Parameters.AddRange(new[]
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
new SQLiteParameter("@hash", hash)
});
 
sqliteCommand.Prepare();
sqliteCommand.Prepare();
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
{
var path = (string)sqlDataReader["Path"];
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 readStream = sqlDataReader.GetStream(2))
{
using (var fileStream = new FileStream(temp, FileMode.Create,
FileAccess.Write,
FileShare.None))
{
using (var fileStream = new FileStream(temp, FileMode.Create,
FileAccess.Write,
FileShare.None))
using (var zipStream =
new GZipStream(readStream, CompressionMode.Decompress))
{
using (var zipStream =
new GZipStream(readStream, CompressionMode.Decompress))
{
zipStream.CopyTo(fileStream);
}
zipStream.CopyTo(fileStream);
}
}
}
 
try
{
File.Replace(temp, path, null, true);
}
catch
{
try
{
File.Replace(temp, path, null, true);
File.Delete(temp);
}
catch
catch (Exception exception)
{
try
{
File.Delete(temp);
}
catch (Exception exception)
{
// Suppress deletion errors of temporary file.
Log.Warning(exception, "Could not delete temporary file.", temp);
}
 
throw;
// Suppress deletion errors of temporary file.
Log.Warning(exception, "Could not delete temporary file.", temp);
}
 
break;
default:
// Asynchronous
using (var readStream = sqlDataReader.GetStream(2))
throw;
}
 
break;
default:
// Asynchronous
using (var readStream = sqlDataReader.GetStream(2))
{
using (var fileStream =
await Miscellaneous.GetFileStream(path, FileMode.Create,
FileAccess.Write,
FileShare.Write,
cancellationToken))
{
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))
{
readStream.Position = 0L;
 
using (var zipStream =
new GZipStream(readStream, CompressionMode.Decompress))
{
await zipStream.CopyToAsync(fileStream);
}
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 RemoveFileFastAsync(IEnumerable<string> hashes, CancellationToken cancellationToken)
public async Task RemoveFileFast(IEnumerable<string> hashes, CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
await sqliteConnection.OpenAsync(cancellationToken);
try
{
var transactionCommands = new List<Task>();
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
try
foreach (var hash in hashes)
{
var transactionCommands = new List<Task>();
 
foreach (var hash in hashes)
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveSnapshotFromHashSql, sqliteConnection, dbTransaction))
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveSnapshotFromHashSql, sqliteConnection, dbTransaction))
sqliteCommand.Parameters.AddRange(new[]
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
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();
 
throw;
}
}
}
}
 
public async Task RemoveFile(string hash, CancellationToken cancellationToken)
{
var connectionString = new SQLiteConnectionStringBuilder
{
ConnectionString = DatabaseConnectionString
};
 
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))
{
try
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
 
sqliteCommand.Prepare();
 
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
}
catch
@@ -917,280 +933,285 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task RemoveFileAsync(string hash, CancellationToken cancellationToken)
public async Task UpdateColor(string hash, Color color, CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateColorFromHashSql, sqliteConnection, dbTransaction))
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveSnapshotFromHashSql, sqliteConnection, dbTransaction))
try
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
new SQLiteParameter("@hash", hash),
new SQLiteParameter("@color", color.ToArgb())
});
 
sqliteCommand.Prepare();
 
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
}
throw;
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task UpdateColorAsync(string hash, Color color, CancellationToken cancellationToken)
public async Task RemoveColor(string hash, CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
var connectionString = new SQLiteConnectionStringBuilder
{
ConnectionString = DatabaseConnectionString
};
 
try
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveColorFromHashSql, sqliteConnection, dbTransaction))
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateColorFromHashSql, sqliteConnection, dbTransaction))
try
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash),
new SQLiteParameter("@color", color.ToArgb())
new SQLiteParameter("@hash", hash)
});
 
sqliteCommand.Prepare();
 
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
}
throw;
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task RemoveColorAsync(string hash, CancellationToken cancellationToken)
public async Task<SnapshotPreview> RetrievePreview(string hash, CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(RetrievePreviewFromHashSql, sqliteConnection))
{
await sqliteConnection.OpenAsync(cancellationToken);
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
var note = string.Empty;
 
sqliteCommand.Prepare();
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RemoveColorFromHashSql, sqliteConnection, dbTransaction))
while (await sqlDataReader.ReadAsync(cancellationToken))
{
sqliteCommand.Parameters.AddRange(new[]
if (!(sqlDataReader["Note"] is DBNull))
{
new SQLiteParameter("@hash", hash)
});
note = (string)sqlDataReader["Note"];
}
 
sqliteCommand.Prepare();
Bitmap shot = null;
 
try
if (!(sqlDataReader["Shot"] is DBNull))
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
var readStream = sqlDataReader.GetStream(2);
 
dbTransaction.Commit();
readStream.Position = 0L;
 
using (var zipStream = new GZipStream(readStream, CompressionMode.Decompress))
{
using (var image = Image.FromStream(zipStream))
{
shot = new Bitmap(image);
}
}
}
catch
{
dbTransaction.Rollback();
 
throw;
}
return new SnapshotPreview(hash, shot, note);
}
 
return null;
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task<SnapshotPreview> RetrievePreviewAsync(string hash, CancellationToken cancellationToken)
/*
public MemoryStream RetrieveFileStream(string hash, CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
 
sqliteConnection.Open();
 
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(RetrieveDataFromHashSql, sqliteConnection))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(RetrievePreviewFromHashSql, sqliteConnection))
sqliteCommand.Parameters.AddRange(new[]
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
new SQLiteParameter("@hash", hash)
});
 
var note = string.Empty;
sqliteCommand.Prepare();
 
sqliteCommand.Prepare();
using (var sqlDataReader = sqliteCommand.ExecuteReader())
{
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
while (sqlDataReader.Read())
{
while (await sqlDataReader.ReadAsync(cancellationToken))
using (var readStream = sqlDataReader.GetStream(1))
{
if (!(sqlDataReader["Note"] is DBNull))
{
note = (string)sqlDataReader["Note"];
}
 
Bitmap shot = null;
 
if (!(sqlDataReader["Shot"] is DBNull))
using (var memoryStream = new MemoryStream())
{
var readStream = sqlDataReader.GetStream(2);
 
readStream.Position = 0L;
 
using (var zipStream = new GZipStream(readStream, CompressionMode.Decompress))
readStream.CopyTo(memoryStream);
 
memoryStream.Position = 0L;
 
using (var zipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
{
using (var image = Image.FromStream(zipStream))
{
shot = new Bitmap(image);
}
 
var outputStream = new MemoryStream();
 
zipStream.CopyTo(outputStream);
 
outputStream.Position = 0L;
 
return outputStream;
}
}
 
return new SnapshotPreview(hash, shot, note);
}
}
 
return null;
}
return null;
}
}
}
finally
{
_databaseLock.Release();
}
}
*/
 
public async Task<MemoryStream> RetrieveFileStreamAsync(string hash, CancellationToken cancellationToken)
public async Task<MemoryStream> RetrieveFileStream(string hash, CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(RetrieveDataFromHashSql, sqliteConnection))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Insert the file change.
using (var sqliteCommand = new SQLiteCommand(RetrieveDataFromHashSql, sqliteConnection))
sqliteCommand.Parameters.AddRange(new[]
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
});
new SQLiteParameter("@hash", hash)
});
 
sqliteCommand.Prepare();
sqliteCommand.Prepare();
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
using (var readStream = sqlDataReader.GetStream(1))
{
using (var readStream = sqlDataReader.GetStream(1))
using (var memoryStream = new MemoryStream())
{
using (var memoryStream = new MemoryStream())
{
readStream.Position = 0L;
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 RelocateFileAsync(string hash, string path, CancellationToken cancellationToken)
public async Task RelocateFile(string hash, string path, CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RelocateFileFromHashSql, sqliteConnection, dbTransaction))
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(RelocateFileFromHashSql, sqliteConnection, dbTransaction))
try
{
sqliteCommand.Parameters.AddRange(new[]
{
@@ -1200,43 +1221,39 @@
 
sqliteCommand.Prepare();
 
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
}
throw;
}
}
}
}
 
finally
{
_databaseLock.Release();
}
}
 
public async Task UpdateNoteAsync(string hash, string note, CancellationToken cancellationToken)
public async Task UpdateNote(string hash, string note, CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateNoteFromHashSql, sqliteConnection, dbTransaction))
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateNoteFromHashSql, sqliteConnection, dbTransaction))
try
{
sqliteCommand.Parameters.AddRange(new[]
{
@@ -1246,166 +1263,157 @@
 
sqliteCommand.Prepare();
 
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
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> UpdateFileAsync(string hash, byte[] data, CancellationToken cancellationToken)
public async Task<string> UpdateFile(string hash, byte[] data, CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
using (var dataMemoryStream = new MemoryStream(data))
{
using (var dataMemoryStream = new MemoryStream(data))
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
try
{
try
using (var md5 = MD5.Create())
{
using (var md5 = MD5.Create())
using (var hashMemoryStream = new MemoryStream())
{
using (var hashMemoryStream = new MemoryStream())
{
dataMemoryStream.Position = 0L;
await dataMemoryStream.CopyToAsync(hashMemoryStream);
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 fileMemoryStream = new MemoryStream())
{
using (var fileZipStream =
new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
{
using (var fileZipStream =
new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
{
dataMemoryStream.Position = 0L;
await dataMemoryStream.CopyToAsync(fileZipStream);
fileZipStream.Close();
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))
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateFileSql, sqliteConnection, dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@dataLength", fileMemoryStream.Length),
new SQLiteParameter("@recomputedHash", hashHex),
new SQLiteParameter("@hash", hash)
});
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))
using (var sqliteCommand =
new SQLiteCommand(GetRowFromHashSql, sqliteConnection,
dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hashHex)
});
new SQLiteParameter("@hash", hashHex)
});
 
sqliteCommand.Prepare();
sqliteCommand.Prepare();
 
using (var sqlDataReader =
await sqliteCommand.ExecuteReaderAsync(cancellationToken))
using (var sqlDataReader =
await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
{
while (await sqlDataReader.ReadAsync(cancellationToken))
if (sqlDataReader["id"] is long rowId)
{
if (sqlDataReader["id"] is long rowId)
using (var sqliteBlob = SQLiteBlob.Create(sqliteConnection,
"main",
"Snapshots",
"Data",
rowId, false))
{
using (var sqliteBlob = SQLiteBlob.Create(
sqliteConnection,
"main",
"Snapshots",
"Data",
rowId, false))
{
var fileMemoryStreamData =
fileMemoryStream.ToArray();
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 UpdateHashAsync(string from, string to, CancellationToken cancellationToken)
public async Task UpdateHash(string from, string to, CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateHashFromHashSql, sqliteConnection, dbTransaction))
{
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateHashFromHashSql, sqliteConnection, dbTransaction))
try
{
sqliteCommand.Parameters.AddRange(new[]
{
@@ -1415,26 +1423,19 @@
 
sqliteCommand.Prepare();
 
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
}
throw;
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
#endregion
@@ -1441,63 +1442,57 @@
 
#region Private Methods
 
private async Task SetAutoVacuum(CancellationToken cancellationToken)
private static async Task SetAutoVacuum(CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection =
new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Set auto vacuum.
using (var sqliteCommand = new SQLiteCommand(SetAutoVacuumSql, sqliteConnection))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
// Set auto vacuum.
using (var sqliteCommand = new SQLiteCommand(SetAutoVacuumSql, sqliteConnection))
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
}
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
}
}
finally
{
_databaseLock.Release();
}
}
 
private async Task CreateDatabase(CancellationToken cancellationToken)
private static async Task CreateDatabase(CancellationToken cancellationToken)
{
await _databaseLock.WaitAsync(cancellationToken);
try
var connectionString = new SQLiteConnectionStringBuilder
{
using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
ConnectionString = DatabaseConnectionString
};
 
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
 
using (var dbTransaction = sqliteConnection.BeginTransaction())
{
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))
{
// Create the table if it does not exist.
using (var sqliteCommand = new SQLiteCommand(CreateTableSql, sqliteConnection, dbTransaction))
try
{
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
dbTransaction.Commit();
}
catch
{
dbTransaction.Rollback();
 
throw;
}
throw;
}
}
}
}
finally
{
_databaseLock.Release();
}
}
 
#endregion