Horizon

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 4  →  ?path2? @ 5
/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,21 +97,28 @@
#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 =>
{
@@ -137,9 +145,6 @@
public void Dispose()
{
_cancellationTokenSource.Cancel();
 
_snapshotSemaphore?.Dispose();
_snapshotSemaphore = null;
}
 
#endregion
@@ -153,6 +158,9 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -163,8 +171,6 @@
using (var sqliteCommand =
new SQLiteCommand(RemoveScreenshotFromHashSql, sqliteConnection, dbTransaction))
{
try
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
@@ -172,6 +178,8 @@
 
sqliteCommand.Prepare();
 
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
@@ -186,6 +194,11 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task NormalizeTime(string hash, CancellationToken cancellationToken)
{
@@ -194,6 +207,9 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
@@ -237,7 +253,8 @@
{
writeSQLiteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@time", dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fff")),
new SQLiteParameter("@time",
dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fff")),
new SQLiteParameter("@hash", hash)
});
 
@@ -260,6 +277,11 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task<long> CountSnapshots(CancellationToken cancellationToken)
{
@@ -268,6 +290,9 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -297,8 +322,13 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task<IEnumerable<Snapshot>> LoadSnapshots(CancellationToken cancellationToken)
public async IAsyncEnumerable<Snapshot> LoadSnapshots([EnumeratorCancellation] CancellationToken cancellationToken)
{
var connectionString = new SQLiteConnectionStringBuilder
{
@@ -305,6 +335,9 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
@@ -317,7 +350,7 @@
 
using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
{
var snapshots = new List<Snapshot>();
//var snapshots = new List<Snapshot>();
while (await sqlDataReader.ReadAsync(cancellationToken))
{
var name = (string)sqlDataReader["Name"];
@@ -342,24 +375,28 @@
}
}
 
snapshots.Add(new Snapshot(name, path, time, hash, color));
yield return 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
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
@@ -443,13 +480,15 @@
(long)await sqliteCommand.ExecuteScalarAsync(cancellationToken);
 
using (var sqliteBlob =
SQLiteBlob.Create(sqliteConnection, "main", "Snapshots", "Data",
SQLiteBlob.Create(sqliteConnection, "main", "Snapshots",
"Data",
rowId,
false))
{
var fileMemoryStreamData = fileMemoryStream.ToArray();
 
sqliteBlob.Write(fileMemoryStreamData, fileMemoryStreamData.Length,
sqliteBlob.Write(fileMemoryStreamData,
fileMemoryStreamData.Length,
0);
}
}
@@ -481,28 +520,31 @@
{
dbTransaction.Rollback();
 
SnapshotCreate?.Invoke(this, new SnapshotCreateFailureEventArgs(name, path, color, exception));
SnapshotCreate?.Invoke(this,
new SnapshotCreateFailureEventArgs(name, path, color, exception));
 
throw;
}
}
}
}
finally
{
_snapshotSemaphore.Release();
_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
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
@@ -599,7 +641,8 @@
cancellationToken);
 
using (var sqliteBlob =
SQLiteBlob.Create(sqliteConnection, "main", "Snapshots",
SQLiteBlob.Create(sqliteConnection, "main",
"Snapshots",
"Data",
rowId,
false))
@@ -612,12 +655,14 @@
}
 
using (var sqliteBlob =
SQLiteBlob.Create(sqliteConnection, "main", "Snapshots",
SQLiteBlob.Create(sqliteConnection, "main",
"Snapshots",
"Shot",
rowId,
false))
{
var bitmapMemoryStreamData = bitmapMemoryStream.ToArray();
var bitmapMemoryStreamData =
bitmapMemoryStream.ToArray();
 
sqliteBlob.Write(bitmapMemoryStreamData,
bitmapMemoryStreamData.Length,
@@ -654,17 +699,19 @@
{
dbTransaction.Rollback();
 
SnapshotCreate?.Invoke(this, new SnapshotCreateFailureEventArgs(name, path, color, exception));
SnapshotCreate?.Invoke(this,
new SnapshotCreateFailureEventArgs(name, path, color, exception));
 
throw;
}
}
}
}
finally
{
_snapshotSemaphore.Release();
_databaseLock.Release();
}
}
}
}
 
public async Task SaveFile(string path, string hash, CancellationToken cancellationToken)
{
@@ -673,6 +720,9 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -720,16 +770,22 @@
}
}
}
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
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -836,13 +892,14 @@
 
throw;
}
}
}
}
finally
{
_snapshotSemaphore.Release();
_databaseLock.Release();
}
}
}
}
 
public async Task RemoveFileFast(IEnumerable<string> hashes, CancellationToken cancellationToken)
{
@@ -851,6 +908,9 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -893,6 +953,11 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task RemoveFile(string hash, CancellationToken cancellationToken)
{
@@ -900,7 +965,9 @@
{
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -911,8 +978,6 @@
using (var sqliteCommand =
new SQLiteCommand(RemoveSnapshotFromHashSql, sqliteConnection, dbTransaction))
{
try
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
@@ -920,6 +985,8 @@
 
sqliteCommand.Prepare();
 
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
@@ -934,6 +1001,11 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task UpdateColor(string hash, Color color, CancellationToken cancellationToken)
{
@@ -942,6 +1014,10 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
 
try
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -952,8 +1028,6 @@
using (var sqliteCommand =
new SQLiteCommand(UpdateColorFromHashSql, sqliteConnection, dbTransaction))
{
try
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash),
@@ -962,6 +1036,8 @@
 
sqliteCommand.Prepare();
 
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
@@ -976,6 +1052,11 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task RemoveColor(string hash, CancellationToken cancellationToken)
{
@@ -984,6 +1065,9 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -994,8 +1078,6 @@
using (var sqliteCommand =
new SQLiteCommand(RemoveColorFromHashSql, sqliteConnection, dbTransaction))
{
try
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash)
@@ -1003,6 +1085,8 @@
 
sqliteCommand.Prepare();
 
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
@@ -1017,6 +1101,11 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task<SnapshotPreview> RetrievePreview(string hash, CancellationToken cancellationToken)
{
@@ -1025,6 +1114,9 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -1075,69 +1167,12 @@
}
}
}
 
/*
public MemoryStream RetrieveFileStream(string hash, CancellationToken cancellationToken)
finally
{
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())
{
 
readStream.Position = 0L;
 
readStream.CopyTo(memoryStream);
 
memoryStream.Position = 0L;
 
using (var zipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
{
 
var outputStream = new MemoryStream();
 
zipStream.CopyTo(outputStream);
 
outputStream.Position = 0L;
 
return outputStream;
_databaseLock.Release();
}
}
}
}
 
return null;
}
}
}
}
*/
 
public async Task<MemoryStream> RetrieveFileStream(string hash, CancellationToken cancellationToken)
{
var connectionString = new SQLiteConnectionStringBuilder
@@ -1145,6 +1180,9 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -1193,6 +1231,11 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task RelocateFile(string hash, string path, CancellationToken cancellationToken)
{
@@ -1201,6 +1244,9 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -1211,8 +1257,6 @@
using (var sqliteCommand =
new SQLiteCommand(RelocateFileFromHashSql, sqliteConnection, dbTransaction))
{
try
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash),
@@ -1221,6 +1265,8 @@
 
sqliteCommand.Prepare();
 
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
@@ -1236,6 +1282,12 @@
}
}
 
finally
{
_databaseLock.Release();
}
}
 
public async Task UpdateNote(string hash, string note, CancellationToken cancellationToken)
{
var connectionString = new SQLiteConnectionStringBuilder
@@ -1243,6 +1295,9 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -1253,8 +1308,6 @@
using (var sqliteCommand =
new SQLiteCommand(UpdateNoteFromHashSql, sqliteConnection, dbTransaction))
{
try
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@hash", hash),
@@ -1263,6 +1316,8 @@
 
sqliteCommand.Prepare();
 
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
@@ -1281,16 +1336,24 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task<string> UpdateFile(string hash, byte[] data, CancellationToken cancellationToken)
{
using (var dataMemoryStream = new MemoryStream(data))
{
var connectionString = new SQLiteConnectionStringBuilder
{
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var dataMemoryStream = new MemoryStream(data))
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -1324,7 +1387,8 @@
 
// Insert the file change.
using (var sqliteCommand =
new SQLiteCommand(UpdateFileSql, sqliteConnection, dbTransaction))
new SQLiteCommand(UpdateFileSql, sqliteConnection,
dbTransaction))
{
sqliteCommand.Parameters.AddRange(new[]
{
@@ -1355,13 +1419,15 @@
{
if (sqlDataReader["id"] is long rowId)
{
using (var sqliteBlob = SQLiteBlob.Create(sqliteConnection,
using (var sqliteBlob = SQLiteBlob.Create(
sqliteConnection,
"main",
"Snapshots",
"Data",
rowId, false))
{
var fileMemoryStreamData = fileMemoryStream.ToArray();
var fileMemoryStreamData =
fileMemoryStream.ToArray();
 
sqliteBlob.Write(fileMemoryStreamData,
fileMemoryStreamData.Length,
@@ -1395,6 +1461,11 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
public async Task UpdateHash(string from, string to, CancellationToken cancellationToken)
{
@@ -1403,6 +1474,9 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -1413,8 +1487,6 @@
using (var sqliteCommand =
new SQLiteCommand(UpdateHashFromHashSql, sqliteConnection, dbTransaction))
{
try
{
sqliteCommand.Parameters.AddRange(new[]
{
new SQLiteParameter("@from", from),
@@ -1423,6 +1495,8 @@
 
sqliteCommand.Prepare();
 
try
{
await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
 
dbTransaction.Commit();
@@ -1437,12 +1511,17 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
#endregion
 
#region Private Methods
 
private static async Task SetAutoVacuum(CancellationToken cancellationToken)
private async Task SetAutoVacuum(CancellationToken cancellationToken)
{
var connectionString = new SQLiteConnectionStringBuilder
{
@@ -1449,6 +1528,9 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection =
new SQLiteConnection(connectionString.ConnectionString))
{
@@ -1461,8 +1543,13 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
private static async Task CreateDatabase(CancellationToken cancellationToken)
private async Task CreateDatabase(CancellationToken cancellationToken)
{
var connectionString = new SQLiteConnectionStringBuilder
{
@@ -1469,6 +1556,9 @@
ConnectionString = DatabaseConnectionString
};
 
await _databaseLock.WaitAsync(cancellationToken);
try
{
using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
{
await sqliteConnection.OpenAsync(cancellationToken);
@@ -1494,6 +1584,11 @@
}
}
}
finally
{
_databaseLock.Release();
}
}
 
#endregion
}