Horizon – Diff between revs 12 and 20

Subversion Repositories:
Rev:
Only display areas with differencesIgnore whitespace
Rev 12 Rev 20
1 using System; 1 using System;
2 using System.Collections.Generic; 2 using System.Collections.Generic;
3 using System.Data.SQLite; 3 using System.Data.SQLite;
4 using System.Drawing; 4 using System.Drawing;
5 using System.Drawing.Imaging; 5 using System.Drawing.Imaging;
6 using System.Globalization; 6 using System.Globalization;
7 using System.IO; 7 using System.IO;
8 using System.IO.Compression; 8 using System.IO.Compression;
9 using System.Runtime.CompilerServices; 9 using System.Runtime.CompilerServices;
10 using System.Security.Cryptography; 10 using System.Security.Cryptography;
11 using System.Threading; 11 using System.Threading;
12 using System.Threading.Tasks; 12 using System.Threading.Tasks;
13 using Horizon.Snapshots; 13 using Horizon.Snapshots;
14 using Horizon.Utilities; 14 using Horizon.Utilities;
15 using Serilog; 15 using Serilog;
16   16  
17 namespace Horizon.Database 17 namespace Horizon.Database
18 { 18 {
19 public class SnapshotDatabase : IDisposable 19 public class SnapshotDatabase : IDisposable
20 { 20 {
21 #region Static Fields and Constants 21 #region Static Fields and Constants
22   22  
23 private const string CreateTableSql = 23 private const string CreateTableSql =
24 "CREATE TABLE IF NOT EXISTS \"Snapshots\" ( \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \"Name\" TEXT NOT NULL, \"Path\" TEXT NOT NULL, \"Time\" TEXT NOT NULL, \"Hash\" TEXT NOT NULL, \"Data\" BLOB, \"Color\" INTEGER, \"Shot\" BLOB, \"Note\" TEXT, UNIQUE (\"Hash\") ON CONFLICT FAIL)"; 24 "CREATE TABLE IF NOT EXISTS \"Snapshots\" ( \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \"Name\" TEXT NOT NULL, \"Path\" TEXT NOT NULL, \"Time\" TEXT NOT NULL, \"Hash\" TEXT NOT NULL, \"Data\" BLOB, \"Color\" INTEGER, \"Shot\" BLOB, \"Note\" TEXT, UNIQUE (\"Hash\") ON CONFLICT FAIL)";
25   25  
26 private const string SetAutoVacuumSql = "PRAGMA auto_vacuum = FULL"; 26 private const string SetAutoVacuumSql = "PRAGMA auto_vacuum = FULL";
27   27  
28 private const string SnapshotFileSql = 28 private const string SnapshotFileSql =
29 "INSERT INTO \"Snapshots\" ( \"Name\", \"Path\", \"Time\", \"Data\", \"Shot\", \"Color\", \"Hash\" ) VALUES ( @name, @path, @time, zeroblob(@dataLength), zeroblob(@shotLength), @color, @hash )"; 29 "INSERT INTO \"Snapshots\" ( \"Name\", \"Path\", \"Time\", \"Data\", \"Shot\", \"Color\", \"Hash\" ) VALUES ( @name, @path, @time, zeroblob(@dataLength), zeroblob(@shotLength), @color, @hash )";
30   30  
31 private const string SnapshotFileNoScreenshotSql = 31 private const string SnapshotFileNoScreenshotSql =
32 "INSERT INTO \"Snapshots\" ( \"Name\", \"Path\", \"Time\", \"Data\", \"Shot\", \"Color\", \"Hash\" ) VALUES ( @name, @path, @time, zeroblob(@dataLength), null, @color, @hash )"; 32 "INSERT INTO \"Snapshots\" ( \"Name\", \"Path\", \"Time\", \"Data\", \"Shot\", \"Color\", \"Hash\" ) VALUES ( @name, @path, @time, zeroblob(@dataLength), null, @color, @hash )";
33   33  
34 private const string RetrieveSnapshotsSql = 34 private const string RetrieveSnapshotsSql =
35 "SELECT \"Name\", \"Path\", \"Time\", \"Color\", \"Hash\" FROM \"Snapshots\" ORDER BY datetime(\"Time\") DESC"; 35 "SELECT \"Name\", \"Path\", \"Time\", \"Color\", \"Hash\" FROM \"Snapshots\" ORDER BY datetime(\"Time\") DESC";
36   36  
37 private const string RetrieveDataPathFromHashSql = 37 private const string RetrieveDataPathFromHashSql =
38 "SELECT \"id\", \"Path\", \"Data\" FROM \"Snapshots\" WHERE Hash = @hash"; 38 "SELECT \"id\", \"Path\", \"Data\" FROM \"Snapshots\" WHERE Hash = @hash";
39   39  
40 private const string RetrieveDataFromHashSql = 40 private const string RetrieveDataFromHashSql =
41 "SELECT \"id\", \"Data\" FROM \"Snapshots\" WHERE Hash = @hash"; 41 "SELECT \"id\", \"Data\" FROM \"Snapshots\" WHERE Hash = @hash";
42   42  
43 private const string UpdateFileSql = 43 private const string UpdateFileSql =
44 "UPDATE \"Snapshots\" SET Data = zeroblob(@dataLength), Hash = @recomputedHash WHERE Hash = @hash"; 44 "UPDATE \"Snapshots\" SET Data = zeroblob(@dataLength), Hash = @recomputedHash WHERE Hash = @hash";
45   45  
46 private const string RemoveSnapshotFromHashSql = 46 private const string RemoveSnapshotFromHashSql =
47 "DELETE FROM \"Snapshots\" WHERE Hash = @hash"; 47 "DELETE FROM \"Snapshots\" WHERE Hash = @hash";
48   48  
49 private const string GetTransferSnapshotFromHashSql = 49 private const string GetTransferSnapshotFromHashSql =
50 "SELECT \"Name\", \"Path\", \"Time\", \"Data\", \"Shot\", \"Color\", \"Hash\", \"Note\" FROM \"Snapshots\" WHERE Hash = @hash"; 50 "SELECT \"Name\", \"Path\", \"Time\", \"Data\", \"Shot\", \"Color\", \"Hash\", \"Note\" FROM \"Snapshots\" WHERE Hash = @hash";
51   51  
52 private const string SetTransferSnapshotSql = 52 private const string SetTransferSnapshotSql =
53 "INSERT INTO \"Snapshots\" ( \"Name\", \"Path\", \"Time\", \"Data\", \"Shot\", \"Color\", \"Hash\", \"Note\" ) VALUES ( @name, @path, @time, zeroblob(@dataLength), zeroblob(@shotLength), @color, @hash, @note )"; 53 "INSERT INTO \"Snapshots\" ( \"Name\", \"Path\", \"Time\", \"Data\", \"Shot\", \"Color\", \"Hash\", \"Note\" ) VALUES ( @name, @path, @time, zeroblob(@dataLength), zeroblob(@shotLength), @color, @hash, @note )";
54   54  
55 private const string RemoveScreenshotFromHashSql = 55 private const string RemoveScreenshotFromHashSql =
56 "UPDATE \"Snapshots\" SET Shot = null WHERE Hash = @hash"; 56 "UPDATE \"Snapshots\" SET Shot = null WHERE Hash = @hash";
57   57  
58 private const string UpdateColorFromHashSql = 58 private const string UpdateColorFromHashSql =
59 "UPDATE \"Snapshots\" SET Color = @color WHERE Hash = @hash"; 59 "UPDATE \"Snapshots\" SET Color = @color WHERE Hash = @hash";
60   60  
61 private const string UpdateNoteFromHashSql = 61 private const string UpdateNoteFromHashSql =
62 "UPDATE \"Snapshots\" SET Note = @note WHERE Hash = @hash"; 62 "UPDATE \"Snapshots\" SET Note = @note WHERE Hash = @hash";
63   63  
64 private const string UpdateHashFromHashSql = "UPDATE \"Snapshots\" SET Hash = @to WHERE Hash = @from"; 64 private const string UpdateHashFromHashSql = "UPDATE \"Snapshots\" SET Hash = @to WHERE Hash = @from";
65   65  
66 private const string RelocateFileFromHashSql = 66 private const string RelocateFileFromHashSql =
67 "UPDATE \"Snapshots\" SET Path = @path WHERE Hash = @hash"; 67 "UPDATE \"Snapshots\" SET Path = @path WHERE Hash = @hash";
68   68  
69 private const string RemoveColorFromHashSql = 69 private const string RemoveColorFromHashSql =
70 "UPDATE \"Snapshots\" SET Color = null WHERE Hash = @hash"; 70 "UPDATE \"Snapshots\" SET Color = null WHERE Hash = @hash";
71   71  
72 private const string RetrievePreviewFromHashSql = 72 private const string RetrievePreviewFromHashSql =
73 "SELECT \"id\", \"Note\", \"Shot\" FROM \"Snapshots\" WHERE Hash = @hash"; 73 "SELECT \"id\", \"Note\", \"Shot\" FROM \"Snapshots\" WHERE Hash = @hash";
74   74  
75 private const string CountSnapshotsSql = "SELECT COUNT(*) FROM \"Snapshots\""; 75 private const string CountSnapshotsSql = "SELECT COUNT(*) FROM \"Snapshots\"";
76   76  
77 private const string GetLastRowInsertSql = "SELECT last_insert_rowid()"; 77 private const string GetLastRowInsertSql = "SELECT last_insert_rowid()";
78   78  
79 private const string GetRowFromHashSql = "SELECT \"id\" FROM \"Snapshots\" WHERE Hash = @hash"; 79 private const string GetRowFromHashSql = "SELECT \"id\" FROM \"Snapshots\" WHERE Hash = @hash";
80   80  
81 private const string RetrieveTimeFromHash = "SELECT \"Time\" FROM \"Snapshots\" WHERE Hash = @hash"; 81 private const string RetrieveTimeFromHash = "SELECT \"Time\" FROM \"Snapshots\" WHERE Hash = @hash";
82   82  
83 private const string UpdateTimeFromHash = "UPDATE \"Snapshots\" SET Time = @time WHERE Hash = @hash"; 83 private const string UpdateTimeFromHash = "UPDATE \"Snapshots\" SET Time = @time WHERE Hash = @hash";
84   84  
85 private static readonly string DatabaseConnectionString = $"Data Source={Constants.DatabaseFilePath};"; 85 private static readonly string DatabaseConnectionString = $"Data Source={Constants.DatabaseFilePath};";
86   86  
87 private static CancellationToken _cancellationToken; 87 private static CancellationToken _cancellationToken;
88   88  
89 #endregion 89 #endregion
90   90  
91 #region Public Events & Delegates 91 #region Public Events & Delegates
92   92  
93 public event EventHandler<SnapshotDataUpdateEventArgs> SnapshotDataUpdate; 93 public event EventHandler<SnapshotDataUpdateEventArgs> SnapshotDataUpdate;
94   94  
95 public event EventHandler<SnapshotNoteUpdateEventArgs> SnapshotNoteUpdate; 95 public event EventHandler<SnapshotNoteUpdateEventArgs> SnapshotNoteUpdate;
96   96  
97 public event EventHandler<SnapshotCreateEventArgs> SnapshotCreate; 97 public event EventHandler<SnapshotCreateEventArgs> SnapshotCreate;
98   98  
99 public event EventHandler<SnapshotRevertEventArgs> SnapshotRevert; 99 public event EventHandler<SnapshotRevertEventArgs> SnapshotRevert;
100   100  
101 #endregion 101 #endregion
102   102  
103 #region Private Delegates, Events, Enums, Properties, Indexers and Fields 103 #region Private Delegates, Events, Enums, Properties, Indexers and Fields
104   104  
105 private readonly CancellationTokenSource _cancellationTokenSource; 105 private readonly CancellationTokenSource _cancellationTokenSource;
106 private readonly SemaphoreSlim _databaseLock; 106 private readonly SemaphoreSlim _databaseLock;
107 private readonly SQLiteConnectionStringBuilder _sqliteConnectionStringBuilder; 107 private readonly SQLiteConnectionStringBuilder _sqliteConnectionStringBuilder;
108   108  
109 #endregion 109 #endregion
110   110  
111 #region Constructors, Destructors and Finalizers 111 #region Constructors, Destructors and Finalizers
112   112  
113 private SnapshotDatabase() 113 private SnapshotDatabase()
114 { 114 {
115 Directory.CreateDirectory(Constants.DatabaseDirectory); 115 Directory.CreateDirectory(Constants.DatabaseDirectory);
116   116  
117 _databaseLock = new SemaphoreSlim(1, 1); 117 _databaseLock = new SemaphoreSlim(1, 1);
118   118  
119 _sqliteConnectionStringBuilder = new SQLiteConnectionStringBuilder 119 _sqliteConnectionStringBuilder = new SQLiteConnectionStringBuilder
120 { 120 {
121 ConnectionString = DatabaseConnectionString 121 ConnectionString = DatabaseConnectionString
122 }; 122 };
123 } 123 }
124   124  
125 public SnapshotDatabase(CancellationToken cancellationToken) : this() 125 public SnapshotDatabase(CancellationToken cancellationToken) : this()
126 { 126 {
127 _cancellationTokenSource = new CancellationTokenSource(); 127 _cancellationTokenSource = new CancellationTokenSource();
128 var localCancellationToken = _cancellationTokenSource.Token; 128 var localCancellationToken = _cancellationTokenSource.Token;
129 var combinedCancellationTokenSource = 129 var combinedCancellationTokenSource =
130 CancellationTokenSource.CreateLinkedTokenSource(localCancellationToken, cancellationToken); 130 CancellationTokenSource.CreateLinkedTokenSource(localCancellationToken, cancellationToken);
131 _cancellationToken = combinedCancellationTokenSource.Token; 131 _cancellationToken = combinedCancellationTokenSource.Token;
132   132  
133 CreateDatabase(_cancellationToken).ContinueWith(async createDatabaseTask => 133 CreateDatabase(_cancellationToken).ContinueWith(async createDatabaseTask =>
134 { 134 {
135 try 135 try
136 { 136 {
137 await createDatabaseTask; 137 await createDatabaseTask;
138   138  
139 try 139 try
140 { 140 {
141 await SetAutoVacuum(_cancellationToken); 141 await SetAutoVacuum(_cancellationToken);
142 } 142 }
143 catch (Exception exception) 143 catch (Exception exception)
144 { 144 {
145 Log.Error(exception, "Unable to set auto vacuum for database."); 145 Log.Error(exception, "Unable to set auto vacuum for database.");
146 } 146 }
147 } 147 }
148 catch (Exception exception) 148 catch (Exception exception)
149 { 149 {
150 Log.Error(exception, "Unable to create database;"); 150 Log.Error(exception, "Unable to create database;");
151 } 151 }
152 }).Wait(_cancellationToken); 152 }).Wait(_cancellationToken);
153 } 153 }
154   154  
155 public void Dispose() 155 public void Dispose()
156 { 156 {
157 _cancellationTokenSource.Cancel(); 157 _cancellationTokenSource.Cancel();
158 } 158 }
159   159  
160 #endregion 160 #endregion
161   161  
162 #region Public Methods 162 #region Public Methods
163   163  
164 public async Task DeleteScreenshotAsync(string hash, CancellationToken cancellationToken) 164 public async Task DeleteScreenshotAsync(string hash, CancellationToken cancellationToken)
165 { 165 {
166 var connectionString = new SQLiteConnectionStringBuilder 166 var connectionString = new SQLiteConnectionStringBuilder
167 { 167 {
168 ConnectionString = DatabaseConnectionString 168 ConnectionString = DatabaseConnectionString
169 }; 169 };
170   170  
171 await _databaseLock.WaitAsync(cancellationToken); 171 await _databaseLock.WaitAsync(cancellationToken);
172 try 172 try
173 { 173 {
174 using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString)) 174 using (var sqliteConnection = new SQLiteConnection(connectionString.ConnectionString))
175 { 175 {
176 await sqliteConnection.OpenAsync(cancellationToken); 176 await sqliteConnection.OpenAsync(cancellationToken);
177   177  
178 using (var dbTransaction = sqliteConnection.BeginTransaction()) 178 using (var dbTransaction = sqliteConnection.BeginTransaction())
179 { 179 {
180 // Insert the file change. 180 // Insert the file change.
181 using (var sqliteCommand = 181 using (var sqliteCommand =
182 new SQLiteCommand(RemoveScreenshotFromHashSql, sqliteConnection, dbTransaction)) 182 new SQLiteCommand(RemoveScreenshotFromHashSql, sqliteConnection, dbTransaction))
183 { 183 {
184 sqliteCommand.Parameters.AddRange(new[] 184 sqliteCommand.Parameters.AddRange(new[]
185 { 185 {
186 new SQLiteParameter("@hash", hash) 186 new SQLiteParameter("@hash", hash)
187 }); 187 });
188   188  
189 sqliteCommand.Prepare(); 189 sqliteCommand.Prepare();
190   190  
191 try 191 try
192 { 192 {
193 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken); 193 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
194   194  
195 dbTransaction.Commit(); 195 dbTransaction.Commit();
196 } 196 }
197 catch 197 catch
198 { 198 {
199 dbTransaction.Rollback(); 199 dbTransaction.Rollback();
200   200  
201 throw; 201 throw;
202 } 202 }
203 } 203 }
204 } 204 }
205 } 205 }
206 } 206 }
207 finally 207 finally
208 { 208 {
209 _databaseLock.Release(); 209 _databaseLock.Release();
210 } 210 }
211 } 211 }
212   212  
213 public async Task NormalizeTimeAsync(string hash, CancellationToken cancellationToken) 213 public async Task NormalizeTimeAsync(string hash, CancellationToken cancellationToken)
214 { 214 {
215 await _databaseLock.WaitAsync(cancellationToken); 215 await _databaseLock.WaitAsync(cancellationToken);
216 try 216 try
217 { 217 {
218 using (var sqliteConnection = 218 using (var sqliteConnection =
219 new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 219 new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
220 { 220 {
221 await sqliteConnection.OpenAsync(cancellationToken); 221 await sqliteConnection.OpenAsync(cancellationToken);
222   222  
223 using (var readSQLiteCommand = new SQLiteCommand(RetrieveTimeFromHash, sqliteConnection)) 223 using (var readSQLiteCommand = new SQLiteCommand(RetrieveTimeFromHash, sqliteConnection))
224 { 224 {
225 readSQLiteCommand.Parameters.AddRange(new[] 225 readSQLiteCommand.Parameters.AddRange(new[]
226 { 226 {
227 new SQLiteParameter("@hash", hash) 227 new SQLiteParameter("@hash", hash)
228 }); 228 });
229   229  
230 readSQLiteCommand.Prepare(); 230 readSQLiteCommand.Prepare();
231   231  
232 using (var sqlDataReader = await readSQLiteCommand.ExecuteReaderAsync(cancellationToken)) 232 using (var sqlDataReader = await readSQLiteCommand.ExecuteReaderAsync(cancellationToken))
233 { 233 {
234 using (var dbTransaction = sqliteConnection.BeginTransaction()) 234 using (var dbTransaction = sqliteConnection.BeginTransaction())
235 { 235 {
236 try 236 try
237 { 237 {
238 while (await sqlDataReader.ReadAsync(cancellationToken)) 238 while (await sqlDataReader.ReadAsync(cancellationToken))
239 { 239 {
240 var time = (string)sqlDataReader["Time"]; 240 var time = (string)sqlDataReader["Time"];
241   241  
242 // Skip if already ISO 8601 242 // Skip if already ISO 8601
243 if (DateTime.TryParseExact(time, 243 if (DateTime.TryParseExact(time,
244 "yyyy-MM-ddTHH:mm:ss.fff", 244 "yyyy-MM-ddTHH:mm:ss.fff",
245 CultureInfo.InvariantCulture, 245 CultureInfo.InvariantCulture,
246 DateTimeStyles.None, out _)) 246 DateTimeStyles.None, out _))
247 { 247 {
248 continue; 248 continue;
249 } 249 }
250   250  
251 if (!DateTime.TryParse(time, out var dateTime)) 251 if (!DateTime.TryParse(time, out var dateTime))
252 { 252 {
253 dateTime = DateTime.Now; 253 dateTime = DateTime.Now;
254 } 254 }
255   255  
256 using (var writeSQLiteCommand = 256 using (var writeSQLiteCommand =
257 new SQLiteCommand(UpdateTimeFromHash, sqliteConnection, dbTransaction)) 257 new SQLiteCommand(UpdateTimeFromHash, sqliteConnection, dbTransaction))
258 { 258 {
259 writeSQLiteCommand.Parameters.AddRange(new[] 259 writeSQLiteCommand.Parameters.AddRange(new[]
260 { 260 {
261 new SQLiteParameter("@time", 261 new SQLiteParameter("@time",
262 dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fff")), 262 dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fff")),
263 new SQLiteParameter("@hash", hash) 263 new SQLiteParameter("@hash", hash)
264 }); 264 });
265   265  
266 writeSQLiteCommand.Prepare(); 266 writeSQLiteCommand.Prepare();
267   267  
268 await writeSQLiteCommand.ExecuteNonQueryAsync(cancellationToken); 268 await writeSQLiteCommand.ExecuteNonQueryAsync(cancellationToken);
269 } 269 }
270 } 270 }
271   271  
272 dbTransaction.Commit(); 272 dbTransaction.Commit();
273 } 273 }
274 catch 274 catch
275 { 275 {
276 dbTransaction.Rollback(); 276 dbTransaction.Rollback();
277   277  
278 throw; 278 throw;
279 } 279 }
280 } 280 }
281 } 281 }
282 } 282 }
283 } 283 }
284 } 284 }
285 finally 285 finally
286 { 286 {
287 _databaseLock.Release(); 287 _databaseLock.Release();
288 } 288 }
289 } 289 }
290   290  
291 public async Task<long> CountSnapshotsAsync(CancellationToken cancellationToken) 291 public async Task<long> CountSnapshotsAsync(CancellationToken cancellationToken)
292 { 292 {
293 await _databaseLock.WaitAsync(cancellationToken); 293 await _databaseLock.WaitAsync(cancellationToken);
294 try 294 try
295 { 295 {
296 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 296 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
297 { 297 {
298 await sqliteConnection.OpenAsync(cancellationToken); 298 await sqliteConnection.OpenAsync(cancellationToken);
299   299  
300 // Insert the file change. 300 // Insert the file change.
301 using (var sqliteCommand = new SQLiteCommand(CountSnapshotsSql, sqliteConnection)) 301 using (var sqliteCommand = new SQLiteCommand(CountSnapshotsSql, sqliteConnection))
302 { 302 {
303 long count = 0; 303 long count = 0;
304   304  
305 sqliteCommand.Prepare(); 305 sqliteCommand.Prepare();
306   306  
307 using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken)) 307 using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
308 { 308 {
309 while (await sqlDataReader.ReadAsync(cancellationToken)) 309 while (await sqlDataReader.ReadAsync(cancellationToken))
310 { 310 {
311 if (!(sqlDataReader[0] is long dbCount)) 311 if (!(sqlDataReader[0] is long dbCount))
312 { 312 {
313 count = -1; 313 count = -1;
314 break; 314 break;
315 } 315 }
316   316  
317 count = dbCount; 317 count = dbCount;
318 } 318 }
319   319  
320 return count; 320 return count;
321 } 321 }
322 } 322 }
323 } 323 }
324 } 324 }
325 finally 325 finally
326 { 326 {
327 _databaseLock.Release(); 327 _databaseLock.Release();
328 } 328 }
329 } 329 }
330   330  
331 public async IAsyncEnumerable<Snapshot> LoadSnapshotsAsync([EnumeratorCancellation] CancellationToken cancellationToken) 331 public async IAsyncEnumerable<Snapshot> LoadSnapshotsAsync([EnumeratorCancellation] CancellationToken cancellationToken)
332 { 332 {
333 await _databaseLock.WaitAsync(cancellationToken); 333 await _databaseLock.WaitAsync(cancellationToken);
334 try 334 try
335 { 335 {
336 using (var sqliteConnection = 336 using (var sqliteConnection =
337 new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 337 new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
338 { 338 {
339 await sqliteConnection.OpenAsync(cancellationToken); 339 await sqliteConnection.OpenAsync(cancellationToken);
340   340  
341 // Insert the file change. 341 // Insert the file change.
342 using (var sqliteCommand = new SQLiteCommand(RetrieveSnapshotsSql, sqliteConnection)) 342 using (var sqliteCommand = new SQLiteCommand(RetrieveSnapshotsSql, sqliteConnection))
343 { 343 {
344 sqliteCommand.Prepare(); 344 sqliteCommand.Prepare();
345   345  
346 using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken)) 346 using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
347 { 347 {
348 //var snapshots = new List<Snapshot>(); 348 //var snapshots = new List<Snapshot>();
349 while (await sqlDataReader.ReadAsync(cancellationToken)) 349 while (await sqlDataReader.ReadAsync(cancellationToken))
350 { 350 {
351 var name = (string)sqlDataReader["Name"]; 351 var name = (string)sqlDataReader["Name"];
352 var path = (string)sqlDataReader["Path"]; 352 var path = (string)sqlDataReader["Path"];
353 var time = (string)sqlDataReader["Time"]; 353 var time = (string)sqlDataReader["Time"];
354 var hash = (string)sqlDataReader["Hash"]; 354 var hash = (string)sqlDataReader["Hash"];
355   355  
356 var color = Color.Empty; 356 var color = Color.Empty;
357   357  
358 if (!(sqlDataReader["Color"] is DBNull)) 358 if (!(sqlDataReader["Color"] is DBNull))
359 { 359 {
360 var dbColor = Convert.ToInt32(sqlDataReader["Color"]); 360 var dbColor = Convert.ToInt32(sqlDataReader["Color"]);
361   361  
362 switch (dbColor) 362 switch (dbColor)
363 { 363 {
364 case 0: 364 case 0:
365 color = Color.Empty; 365 color = Color.Empty;
366 break; 366 break;
367 default: 367 default:
368 color = Color.FromArgb(dbColor); 368 color = Color.FromArgb(dbColor);
369 break; 369 break;
370 } 370 }
371 } 371 }
372   372  
373 yield return new Snapshot(name, path, time, hash, color); 373 yield return new Snapshot(name, path, time, hash, color);
374 } 374 }
375 } 375 }
376 } 376 }
377 } 377 }
378 } 378 }
379 finally 379 finally
380 { 380 {
381 _databaseLock.Release(); 381 _databaseLock.Release();
382 } 382 }
383 } 383 }
384   384  
385 public async Task SetCompleteSnapshotAsync(TransferSnapshot transferSnapshot, CancellationToken cancellationToken) 385 public async Task ApplyTransferSnapshotAsync(TransferSnapshot transferSnapshot, CancellationToken cancellationToken)
386 { 386 {
387 await _databaseLock.WaitAsync(cancellationToken); 387 await _databaseLock.WaitAsync(cancellationToken);
388 try 388 try
389 { 389 {
390 using (var sqliteConnection = 390 using (var sqliteConnection =
391 new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 391 new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
392 { 392 {
393 await sqliteConnection.OpenAsync(cancellationToken); 393 await sqliteConnection.OpenAsync(cancellationToken);
-   394  
394   395 var color = Color.Empty;
395 using (var dbTransaction = sqliteConnection.BeginTransaction()) 396 using (var dbTransaction = sqliteConnection.BeginTransaction())
396 { 397 {
397 try 398 try
398 { 399 {
399 using (var dataMemoryStream = new MemoryStream()) 400 using (var dataMemoryStream = new MemoryStream())
400 { 401 {
401 using (var dataZipStream = 402 using (var dataZipStream =
402 new GZipStream(dataMemoryStream, CompressionMode.Compress, true)) 403 new GZipStream(dataMemoryStream, CompressionMode.Compress, true))
403 { 404 {
404 dataMemoryStream.Position = 0L; 405 dataMemoryStream.Position = 0L;
405 await dataZipStream.WriteAsync(transferSnapshot.Data, 0, 406 await dataZipStream.WriteAsync(transferSnapshot.Data, 0,
406 transferSnapshot.Data.Length, cancellationToken); 407 transferSnapshot.Data.Length, cancellationToken);
407 dataZipStream.Close(); 408 dataZipStream.Close();
408   409  
409 using (var bitmapMemoryStream = new MemoryStream()) 410 using (var bitmapMemoryStream = new MemoryStream())
410 { 411 {
411 bitmapMemoryStream.Position = 0L; 412 bitmapMemoryStream.Position = 0L;
412 using (var bitmapZipStream = 413 using (var bitmapZipStream =
413 new GZipStream(bitmapMemoryStream, CompressionMode.Compress, 414 new GZipStream(bitmapMemoryStream, CompressionMode.Compress,
414 true)) 415 true))
415 { 416 {
416 using (var transferImageStream = new MemoryStream(transferSnapshot.Shot)) 417 using (var transferImageStream = new MemoryStream(transferSnapshot.Shot))
417 { 418 {
418 transferImageStream.Position = 0L; 419 transferImageStream.Position = 0L;
419 await transferImageStream.CopyToAsync(bitmapZipStream); 420 await transferImageStream.CopyToAsync(bitmapZipStream);
420 bitmapZipStream.Close(); 421 bitmapZipStream.Close();
421 bitmapMemoryStream.Position = 0L; 422 bitmapMemoryStream.Position = 0L;
422   423  
423 var a = bitmapMemoryStream.ToArray(); 424 var a = bitmapMemoryStream.ToArray();
424   425  
425 // Insert the file change. 426 // Insert the file change.
426 using (var sqliteCommand = 427 using (var sqliteCommand =
427 new SQLiteCommand(SetTransferSnapshotSql, sqliteConnection, 428 new SQLiteCommand(SetTransferSnapshotSql, sqliteConnection,
428 dbTransaction)) 429 dbTransaction))
429 { 430 {
430 sqliteCommand.Parameters.AddRange(new[] 431 sqliteCommand.Parameters.AddRange(new[]
431 { 432 {
432 new SQLiteParameter("@name", transferSnapshot.Name), 433 new SQLiteParameter("@name", transferSnapshot.Name),
433 new SQLiteParameter("@path", transferSnapshot.Path), 434 new SQLiteParameter("@path", transferSnapshot.Path),
434 new SQLiteParameter("@time", transferSnapshot.Time), 435 new SQLiteParameter("@time", transferSnapshot.Time),
435 new SQLiteParameter("@dataLength", 436 new SQLiteParameter("@dataLength",
436 dataMemoryStream.Length), 437 dataMemoryStream.Length),
437 new SQLiteParameter("@shotLength", 438 new SQLiteParameter("@shotLength",
438 bitmapMemoryStream.Length), 439 bitmapMemoryStream.Length),
439 new SQLiteParameter("@hash", transferSnapshot.Hash), 440 new SQLiteParameter("@hash", transferSnapshot.Hash),
440 new SQLiteParameter("@note", transferSnapshot.Note) 441 new SQLiteParameter("@note", transferSnapshot.Note)
441 }); 442 });
442   443  
443 var numeric = transferSnapshot.Color; 444 var numeric = transferSnapshot.Color;
444 switch (numeric) 445 switch (numeric)
445 { 446 {
446 case 0: 447 case 0:
447 sqliteCommand.Parameters.Add( 448 sqliteCommand.Parameters.Add(
448 new SQLiteParameter("@color", null)); 449 new SQLiteParameter("@color", DBNull.Value));
-   450 color = Color.Empty;
449 break; 451 break;
450 default: 452 default:
451 sqliteCommand.Parameters.Add( 453 sqliteCommand.Parameters.Add(
452 new SQLiteParameter("@color", numeric)); 454 new SQLiteParameter("@color", numeric));
-   455 color = Color.FromArgb(transferSnapshot.Color);
453 break; 456 break;
454 } 457 }
455   458  
456 sqliteCommand.Prepare(); 459 sqliteCommand.Prepare();
457   460  
458 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken); 461 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
459 } 462 }
460   463  
461 // Insert the data blobs. 464 // Insert the data blobs.
462 using (var sqliteCommand = 465 using (var sqliteCommand =
463 new SQLiteCommand(GetLastRowInsertSql, sqliteConnection, 466 new SQLiteCommand(GetLastRowInsertSql, sqliteConnection,
464 dbTransaction)) 467 dbTransaction))
465 { 468 {
466 sqliteCommand.Prepare(); 469 sqliteCommand.Prepare();
467   470  
468 var rowId = 471 var rowId =
469 (long)await sqliteCommand.ExecuteScalarAsync( 472 (long)await sqliteCommand.ExecuteScalarAsync(
470 cancellationToken); 473 cancellationToken);
471   474  
472 using (var sqliteBlob = 475 using (var sqliteBlob =
473 SQLiteBlob.Create(sqliteConnection, "main", 476 SQLiteBlob.Create(sqliteConnection, "main",
474 "Snapshots", 477 "Snapshots",
475 "Data", 478 "Data",
476 rowId, 479 rowId,
477 false)) 480 false))
478 { 481 {
479 var fileMemoryStreamData = dataMemoryStream.ToArray(); 482 var fileMemoryStreamData = dataMemoryStream.ToArray();
480   483  
481 sqliteBlob.Write(fileMemoryStreamData, 484 sqliteBlob.Write(fileMemoryStreamData,
482 fileMemoryStreamData.Length, 485 fileMemoryStreamData.Length,
483 0); 486 0);
484 } 487 }
485   488  
486 using (var sqliteBlob = 489 using (var sqliteBlob =
487 SQLiteBlob.Create(sqliteConnection, "main", 490 SQLiteBlob.Create(sqliteConnection, "main",
488 "Snapshots", 491 "Snapshots",
489 "Shot", 492 "Shot",
490 rowId, 493 rowId,
491 false)) 494 false))
492 { 495 {
493 var bitmapMemoryStreamData = 496 var bitmapMemoryStreamData =
494 bitmapMemoryStream.ToArray(); 497 bitmapMemoryStream.ToArray();
495   498  
496 sqliteBlob.Write(bitmapMemoryStreamData, 499 sqliteBlob.Write(bitmapMemoryStreamData,
497 bitmapMemoryStreamData.Length, 500 bitmapMemoryStreamData.Length,
498 0); 501 0);
499 } 502 }
500 } 503 }
501   504  
502 dbTransaction.Commit(); 505 dbTransaction.Commit();
503   506  
504 SnapshotCreate?.Invoke(this, 507 SnapshotCreate?.Invoke(this,
505 new SnapshotCreateSuccessEventArgs(transferSnapshot.Name, 508 new SnapshotCreateSuccessEventArgs(
-   509 transferSnapshot.Name,
506 transferSnapshot.Time, transferSnapshot.Path, 510 transferSnapshot.Time,
507 Color.FromArgb(transferSnapshot.Color), 511 transferSnapshot.Path,
-   512 color,
508 transferSnapshot.Hash)); 513 transferSnapshot.Hash)
-   514 );
509 } 515 }
510 } 516 }
511 } 517 }
512 } 518 }
513 } 519 }
514 } 520 }
515 catch (SQLiteException exception) 521 catch (SQLiteException exception)
516 { 522 {
517 dbTransaction.Rollback(); 523 dbTransaction.Rollback();
518   524  
519 if (exception.ResultCode != SQLiteErrorCode.Constraint) 525 if (exception.ResultCode != SQLiteErrorCode.Constraint)
520 { 526 {
521 SnapshotCreate?.Invoke(this, 527 SnapshotCreate?.Invoke(this,
522 new SnapshotCreateFailureEventArgs(transferSnapshot.Name, transferSnapshot.Path, 528 new SnapshotCreateFailureEventArgs(
-   529 transferSnapshot.Name,
523 Color.FromArgb(transferSnapshot.Color), exception)); 530 transferSnapshot.Path,
-   531 color,
-   532 exception)
-   533 );
524 } 534 }
525   535  
526 throw; 536 throw;
527 } 537 }
528 catch (Exception exception) 538 catch (Exception exception)
529 { 539 {
530 dbTransaction.Rollback(); 540 dbTransaction.Rollback();
531   541  
532 SnapshotCreate?.Invoke(this, 542 SnapshotCreate?.Invoke(this,
533 new SnapshotCreateFailureEventArgs(transferSnapshot.Name, transferSnapshot.Path, 543 new SnapshotCreateFailureEventArgs(
534 Color.FromArgb(transferSnapshot.Color), exception)); 544 transferSnapshot.Name,
-   545 transferSnapshot.Path,
-   546 color,
-   547 exception)
-   548 );
535   549  
536 throw; 550 throw;
537 } 551 }
538 } 552 }
539 } 553 }
540 } 554 }
541 finally 555 finally
542 { 556 {
543 _databaseLock.Release(); 557 _databaseLock.Release();
544 } 558 }
545 } 559 }
546   560  
547   561  
548 public async Task<TransferSnapshot> GetCompleteSnapshot(string hash, CancellationToken cancellationToken) 562 public async Task<TransferSnapshot> GenerateTransferSnapshotAsync(string hash, CancellationToken cancellationToken)
549 { 563 {
550 await _databaseLock.WaitAsync(cancellationToken); 564 await _databaseLock.WaitAsync(cancellationToken);
551 try 565 try
552 { 566 {
553 using (var sqliteConnection = 567 using (var sqliteConnection =
554 new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 568 new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
555 { 569 {
556 await sqliteConnection.OpenAsync(cancellationToken); 570 await sqliteConnection.OpenAsync(cancellationToken);
557   571  
558 // Insert the file change. 572 // Insert the file change.
559 using (var sqliteCommand = new SQLiteCommand(GetTransferSnapshotFromHashSql, sqliteConnection)) 573 using (var sqliteCommand = new SQLiteCommand(GetTransferSnapshotFromHashSql, sqliteConnection))
560 { 574 {
561   575  
562 sqliteCommand.Parameters.AddRange(new[] 576 sqliteCommand.Parameters.AddRange(new[]
563 { 577 {
564 new SQLiteParameter("@hash", hash) 578 new SQLiteParameter("@hash", hash)
565 }); 579 });
566   580  
567 sqliteCommand.Prepare(); 581 sqliteCommand.Prepare();
568   582  
569 using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken)) 583 using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
570 { 584 {
571 //var snapshots = new List<Snapshot>(); 585 //var snapshots = new List<Snapshot>();
572 while (await sqlDataReader.ReadAsync(cancellationToken)) 586 while (await sqlDataReader.ReadAsync(cancellationToken))
573 { 587 {
574 var name = (string)sqlDataReader["Name"]; 588 var name = (string)sqlDataReader["Name"];
575 var path = (string)sqlDataReader["Path"]; 589 var path = (string)sqlDataReader["Path"];
576 var time = (string)sqlDataReader["Time"]; 590 var time = (string)sqlDataReader["Time"];
577   591  
578 var color = Color.Empty; 592 var color = Color.Empty;
579   593  
580 if (!(sqlDataReader["Color"] is DBNull)) 594 if (!(sqlDataReader["Color"] is DBNull))
581 { 595 {
582 var dbColor = Convert.ToInt32(sqlDataReader["Color"]); 596 var dbColor = Convert.ToInt32(sqlDataReader["Color"]);
583   597  
584 switch (dbColor) 598 switch (dbColor)
585 { 599 {
586 case 0: 600 case 0:
587 color = Color.Empty; 601 color = Color.Empty;
588 break; 602 break;
589 default: 603 default:
590 color = Color.FromArgb(dbColor); 604 color = Color.FromArgb(dbColor);
591 break; 605 break;
592 } 606 }
593 } 607 }
594   608  
595 var note = string.Empty; 609 var note = string.Empty;
596   610  
597 if (!(sqlDataReader["Note"] is DBNull)) 611 if (!(sqlDataReader["Note"] is DBNull))
598 { 612 {
599 note = (string)sqlDataReader["Note"]; 613 note = (string)sqlDataReader["Note"];
600 } 614 }
601   615  
602 Bitmap shot = null; 616 Bitmap shot = null;
603   617  
604 if (!(sqlDataReader["Shot"] is DBNull)) 618 if (!(sqlDataReader["Shot"] is DBNull))
605 { 619 {
606 var readStream = sqlDataReader.GetStream(4); 620 var readStream = sqlDataReader.GetStream(4);
607   621  
608 readStream.Position = 0L; 622 readStream.Position = 0L;
609   623  
610 using (var zipStream = new GZipStream(readStream, CompressionMode.Decompress)) 624 using (var zipStream = new GZipStream(readStream, CompressionMode.Decompress))
611 { 625 {
612 using (var image = Image.FromStream(zipStream)) 626 using (var image = Image.FromStream(zipStream))
613 { 627 {
614 shot = new Bitmap(image); 628 shot = new Bitmap(image);
615 } 629 }
616 } 630 }
617 } 631 }
618   632  
619 byte[] data = null; 633 byte[] data = null;
620 if (!(sqlDataReader["Data"] is DBNull)) 634 if (!(sqlDataReader["Data"] is DBNull))
621 { 635 {
622 using (var readStream = sqlDataReader.GetStream(3)) 636 using (var readStream = sqlDataReader.GetStream(3))
623 { 637 {
624 using (var memoryStream = new MemoryStream()) 638 using (var memoryStream = new MemoryStream())
625 { 639 {
626 readStream.Position = 0L; 640 readStream.Position = 0L;
627   641  
628 await readStream.CopyToAsync(memoryStream); 642 await readStream.CopyToAsync(memoryStream);
629   643  
630 memoryStream.Position = 0L; 644 memoryStream.Position = 0L;
631   645  
632 using (var zipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) 646 using (var zipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
633 { 647 {
634 // Do not dispose the returned stream and leave it up to callers to dispose. 648 // Do not dispose the returned stream and leave it up to callers to dispose.
635 var outputStream = new MemoryStream(); 649 var outputStream = new MemoryStream();
636   650  
637 await zipStream.CopyToAsync(outputStream); 651 await zipStream.CopyToAsync(outputStream);
638   652  
639 outputStream.Position = 0L; 653 outputStream.Position = 0L;
640   654  
641 data = outputStream.ToArray(); 655 data = outputStream.ToArray();
642 } 656 }
643 } 657 }
644 } 658 }
645 } 659 }
646   660  
647 return new TransferSnapshot(name, path, time, hash, color, shot, note, data); 661 return new TransferSnapshot(name, path, time, hash, color, shot, note, data);
648 } 662 }
649 } 663 }
650 } 664 }
651 } 665 }
652 } 666 }
653 finally 667 finally
654 { 668 {
655 _databaseLock.Release(); 669 _databaseLock.Release();
656 } 670 }
657   671  
658 return null; 672 return null;
659 } 673 }
660   674  
661 public async Task CreateSnapshotAsync(string name, string path, Color color, CancellationToken cancellationToken) 675 public async Task CreateSnapshotAsync(string name, string path, Color color, CancellationToken cancellationToken)
662 { 676 {
663 await _databaseLock.WaitAsync(cancellationToken); 677 await _databaseLock.WaitAsync(cancellationToken);
664 try 678 try
665 { 679 {
666 using (var sqliteConnection = 680 using (var sqliteConnection =
667 new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 681 new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
668 { 682 {
669 await sqliteConnection.OpenAsync(cancellationToken); 683 await sqliteConnection.OpenAsync(cancellationToken);
670   684  
671 using (var dbTransaction = sqliteConnection.BeginTransaction()) 685 using (var dbTransaction = sqliteConnection.BeginTransaction())
672 { 686 {
673 try 687 try
674 { 688 {
675 using (var md5 = MD5.Create()) 689 using (var md5 = MD5.Create())
676 { 690 {
677 using (var hashMemoryStream = new MemoryStream()) 691 using (var hashMemoryStream = new MemoryStream())
678 { 692 {
679 using (var fileStream = 693 using (var fileStream =
680 await Miscellaneous.GetFileStream(path, FileMode.Open, FileAccess.Read, 694 await Miscellaneous.GetFileStream(path, FileMode.Open, FileAccess.Read,
681 FileShare.Read, 695 FileShare.Read,
682 cancellationToken)) 696 cancellationToken))
683 { 697 {
684 fileStream.Position = 0L; 698 fileStream.Position = 0L;
685 await fileStream.CopyToAsync(hashMemoryStream); 699 await fileStream.CopyToAsync(hashMemoryStream);
686   700  
687 hashMemoryStream.Position = 0L; 701 hashMemoryStream.Position = 0L;
688 var hash = md5.ComputeHash(hashMemoryStream); 702 var hash = md5.ComputeHash(hashMemoryStream);
689 var hashHex = BitConverter.ToString(hash).Replace("-", "") 703 var hashHex = BitConverter.ToString(hash).Replace("-", "")
690 .ToLowerInvariant(); 704 .ToLowerInvariant();
691   705  
692 using (var fileMemoryStream = new MemoryStream()) 706 using (var fileMemoryStream = new MemoryStream())
693 { 707 {
694 using (var fileZipStream = 708 using (var fileZipStream =
695 new GZipStream(fileMemoryStream, CompressionMode.Compress, true)) 709 new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
696 { 710 {
697 fileStream.Position = 0L; 711 fileStream.Position = 0L;
698 await fileStream.CopyToAsync(fileZipStream); 712 await fileStream.CopyToAsync(fileZipStream);
699 fileZipStream.Close(); 713 fileZipStream.Close();
700   714  
701 var time = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fff"); 715 var time = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fff");
702   716  
703 fileMemoryStream.Position = 0L; 717 fileMemoryStream.Position = 0L;
704   718  
705 // Insert the file change. 719 // Insert the file change.
706 using (var sqliteCommand = 720 using (var sqliteCommand =
707 new SQLiteCommand(SnapshotFileNoScreenshotSql, sqliteConnection, 721 new SQLiteCommand(SnapshotFileNoScreenshotSql, sqliteConnection,
708 dbTransaction)) 722 dbTransaction))
709 { 723 {
710 sqliteCommand.Parameters.AddRange(new[] 724 sqliteCommand.Parameters.AddRange(new[]
711 { 725 {
712 new SQLiteParameter("@name", name), 726 new SQLiteParameter("@name", name),
713 new SQLiteParameter("@time", time), 727 new SQLiteParameter("@time", time),
714 new SQLiteParameter("@path", path), 728 new SQLiteParameter("@path", path),
715 new SQLiteParameter("@dataLength", 729 new SQLiteParameter("@dataLength",
716 fileMemoryStream.Length), 730 fileMemoryStream.Length),
717 new SQLiteParameter("@hash", hashHex) 731 new SQLiteParameter("@hash", hashHex)
718 }); 732 });
719   733  
720 var numeric = color.ToArgb(); 734 var numeric = color.ToArgb();
721 switch (numeric) 735 switch (numeric)
722 { 736 {
723 case 0: 737 case 0:
724 sqliteCommand.Parameters.Add( 738 sqliteCommand.Parameters.Add(
725 new SQLiteParameter("@color", null)); 739 new SQLiteParameter("@color", DBNull.Value));
726 break; 740 break;
727 default: 741 default:
728 sqliteCommand.Parameters.Add( 742 sqliteCommand.Parameters.Add(
729 new SQLiteParameter("@color", numeric)); 743 new SQLiteParameter("@color", numeric));
730 break; 744 break;
731 } 745 }
732   746  
733 sqliteCommand.Prepare(); 747 sqliteCommand.Prepare();
734   748  
735 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken); 749 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
736 } 750 }
737   751  
738 // Insert the data blobs. 752 // Insert the data blobs.
739 using (var sqliteCommand = 753 using (var sqliteCommand =
740 new SQLiteCommand(GetLastRowInsertSql, sqliteConnection, 754 new SQLiteCommand(GetLastRowInsertSql, sqliteConnection,
741 dbTransaction)) 755 dbTransaction))
742 { 756 {
743 sqliteCommand.Prepare(); 757 sqliteCommand.Prepare();
744   758  
745 var rowId = 759 var rowId =
746 (long)await sqliteCommand.ExecuteScalarAsync(cancellationToken); 760 (long)await sqliteCommand.ExecuteScalarAsync(cancellationToken);
747   761  
748 using (var sqliteBlob = 762 using (var sqliteBlob =
749 SQLiteBlob.Create(sqliteConnection, "main", "Snapshots", 763 SQLiteBlob.Create(sqliteConnection, "main", "Snapshots",
750 "Data", 764 "Data",
751 rowId, 765 rowId,
752 false)) 766 false))
753 { 767 {
754 var fileMemoryStreamData = fileMemoryStream.ToArray(); 768 var fileMemoryStreamData = fileMemoryStream.ToArray();
755   769  
756 sqliteBlob.Write(fileMemoryStreamData, 770 sqliteBlob.Write(fileMemoryStreamData,
757 fileMemoryStreamData.Length, 771 fileMemoryStreamData.Length,
758 0); 772 0);
759 } 773 }
760 } 774 }
761   775  
762 dbTransaction.Commit(); 776 dbTransaction.Commit();
763   777  
764 SnapshotCreate?.Invoke(this, 778 SnapshotCreate?.Invoke(this,
765 new SnapshotCreateSuccessEventArgs(name, time, path, color, 779 new SnapshotCreateSuccessEventArgs(name, time, path, color,
766 hashHex)); 780 hashHex));
767 } 781 }
768 } 782 }
769 } 783 }
770 } 784 }
771 } 785 }
772 } 786 }
773 catch (SQLiteException exception) 787 catch (SQLiteException exception)
774 { 788 {
775 dbTransaction.Rollback(); 789 dbTransaction.Rollback();
776   790  
777 if (exception.ResultCode != SQLiteErrorCode.Constraint) 791 if (exception.ResultCode != SQLiteErrorCode.Constraint)
778 { 792 {
779 SnapshotCreate?.Invoke(this, 793 SnapshotCreate?.Invoke(this,
780 new SnapshotCreateFailureEventArgs(name, path, color, exception)); 794 new SnapshotCreateFailureEventArgs(name, path, color, exception));
781 } 795 }
782   796  
783 throw; 797 throw;
784 } 798 }
785 catch (Exception exception) 799 catch (Exception exception)
786 { 800 {
787 dbTransaction.Rollback(); 801 dbTransaction.Rollback();
788   802  
789 SnapshotCreate?.Invoke(this, 803 SnapshotCreate?.Invoke(this,
790 new SnapshotCreateFailureEventArgs(name, path, color, exception)); 804 new SnapshotCreateFailureEventArgs(name, path, color, exception));
791   805  
792 throw; 806 throw;
793 } 807 }
794 } 808 }
795 } 809 }
796 } 810 }
797 finally 811 finally
798 { 812 {
799 _databaseLock.Release(); 813 _databaseLock.Release();
800 } 814 }
801 } 815 }
802   816  
803 public async Task CreateSnapshotAsync(string name, string path, 817 public async Task CreateSnapshotAsync(string name, string path,
804 Bitmap shot, Color color, CancellationToken cancellationToken) 818 Bitmap shot, Color color, CancellationToken cancellationToken)
805 { 819 {
806 await _databaseLock.WaitAsync(cancellationToken); 820 await _databaseLock.WaitAsync(cancellationToken);
807 try 821 try
808 { 822 {
809 using (var sqliteConnection = 823 using (var sqliteConnection =
810 new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 824 new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
811 { 825 {
812 await sqliteConnection.OpenAsync(cancellationToken); 826 await sqliteConnection.OpenAsync(cancellationToken);
813   827  
814 using (var dbTransaction = sqliteConnection.BeginTransaction()) 828 using (var dbTransaction = sqliteConnection.BeginTransaction())
815 { 829 {
816 try 830 try
817 { 831 {
818 using (var md5 = MD5.Create()) 832 using (var md5 = MD5.Create())
819 { 833 {
820 using (var hashMemoryStream = new MemoryStream()) 834 using (var hashMemoryStream = new MemoryStream())
821 { 835 {
822 using (var fileStream = 836 using (var fileStream =
823 await Miscellaneous.GetFileStream(path, FileMode.Open, FileAccess.Read, 837 await Miscellaneous.GetFileStream(path, FileMode.Open, FileAccess.Read,
824 FileShare.Read, 838 FileShare.Read,
825 cancellationToken)) 839 cancellationToken))
826 { 840 {
827 fileStream.Position = 0L; 841 fileStream.Position = 0L;
828 await fileStream.CopyToAsync(hashMemoryStream); 842 await fileStream.CopyToAsync(hashMemoryStream);
829   843  
830 hashMemoryStream.Position = 0L; 844 hashMemoryStream.Position = 0L;
831 var hash = md5.ComputeHash(hashMemoryStream); 845 var hash = md5.ComputeHash(hashMemoryStream);
832 var hashHex = BitConverter.ToString(hash).Replace("-", "") 846 var hashHex = BitConverter.ToString(hash).Replace("-", "")
833 .ToLowerInvariant(); 847 .ToLowerInvariant();
834   848  
835 using (var fileMemoryStream = new MemoryStream()) 849 using (var fileMemoryStream = new MemoryStream())
836 { 850 {
837 using (var fileZipStream = 851 using (var fileZipStream =
838 new GZipStream(fileMemoryStream, CompressionMode.Compress, true)) 852 new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
839 { 853 {
840 fileStream.Position = 0L; 854 fileStream.Position = 0L;
841 await fileStream.CopyToAsync(fileZipStream); 855 await fileStream.CopyToAsync(fileZipStream);
842 fileZipStream.Close(); 856 fileZipStream.Close();
843   857  
844 using (var bitmapMemoryStream = new MemoryStream()) 858 using (var bitmapMemoryStream = new MemoryStream())
845 { 859 {
846 using (var bitmapZipStream = 860 using (var bitmapZipStream =
847 new GZipStream(bitmapMemoryStream, CompressionMode.Compress, 861 new GZipStream(bitmapMemoryStream, CompressionMode.Compress,
848 true)) 862 true))
849 { 863 {
850 shot.Save(bitmapZipStream, ImageFormat.Bmp); 864 shot.Save(bitmapZipStream, ImageFormat.Bmp);
851 bitmapZipStream.Close(); 865 bitmapZipStream.Close();
852   866  
853 var time = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fff"); 867 var time = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fff");
854   868  
855 fileMemoryStream.Position = 0L; 869 fileMemoryStream.Position = 0L;
856 bitmapMemoryStream.Position = 0L; 870 bitmapMemoryStream.Position = 0L;
857   871  
858 // Insert the file change. 872 // Insert the file change.
859 using (var sqliteCommand = 873 using (var sqliteCommand =
860 new SQLiteCommand(SnapshotFileSql, sqliteConnection, 874 new SQLiteCommand(SnapshotFileSql, sqliteConnection,
861 dbTransaction)) 875 dbTransaction))
862 { 876 {
863 sqliteCommand.Parameters.AddRange(new[] 877 sqliteCommand.Parameters.AddRange(new[]
864 { 878 {
865 new SQLiteParameter("@name", name), 879 new SQLiteParameter("@name", name),
866 new SQLiteParameter("@time", time), 880 new SQLiteParameter("@time", time),
867 new SQLiteParameter("@path", path), 881 new SQLiteParameter("@path", path),
868 new SQLiteParameter("@shotLength", 882 new SQLiteParameter("@shotLength",
869 bitmapMemoryStream.Length), 883 bitmapMemoryStream.Length),
870 new SQLiteParameter("@dataLength", 884 new SQLiteParameter("@dataLength",
871 fileMemoryStream.Length), 885 fileMemoryStream.Length),
872 new SQLiteParameter("@hash", hashHex) 886 new SQLiteParameter("@hash", hashHex)
873 }); 887 });
874   888  
875 var numeric = color.ToArgb(); 889 var numeric = color.ToArgb();
876 switch (numeric) 890 switch (numeric)
877 { 891 {
878 case 0: 892 case 0:
879 sqliteCommand.Parameters.Add( 893 sqliteCommand.Parameters.Add(
880 new SQLiteParameter("@color", null)); 894 new SQLiteParameter("@color", DBNull.Value));
881 break; 895 break;
882 default: 896 default:
883 sqliteCommand.Parameters.Add( 897 sqliteCommand.Parameters.Add(
884 new SQLiteParameter("@color", numeric)); 898 new SQLiteParameter("@color", numeric));
885 break; 899 break;
886 } 900 }
887   901  
888 sqliteCommand.Prepare(); 902 sqliteCommand.Prepare();
889   903  
890 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken); 904 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
891 } 905 }
892   906  
893 // Insert the data blobs. 907 // Insert the data blobs.
894 using (var sqliteCommand = 908 using (var sqliteCommand =
895 new SQLiteCommand(GetLastRowInsertSql, sqliteConnection, 909 new SQLiteCommand(GetLastRowInsertSql, sqliteConnection,
896 dbTransaction)) 910 dbTransaction))
897 { 911 {
898 sqliteCommand.Prepare(); 912 sqliteCommand.Prepare();
899   913  
900 var rowId = 914 var rowId =
901 (long)await sqliteCommand.ExecuteScalarAsync( 915 (long)await sqliteCommand.ExecuteScalarAsync(
902 cancellationToken); 916 cancellationToken);
903   917  
904 using (var sqliteBlob = 918 using (var sqliteBlob =
905 SQLiteBlob.Create(sqliteConnection, "main", 919 SQLiteBlob.Create(sqliteConnection, "main",
906 "Snapshots", 920 "Snapshots",
907 "Data", 921 "Data",
908 rowId, 922 rowId,
909 false)) 923 false))
910 { 924 {
911 var fileMemoryStreamData = fileMemoryStream.ToArray(); 925 var fileMemoryStreamData = fileMemoryStream.ToArray();
912   926  
913 sqliteBlob.Write(fileMemoryStreamData, 927 sqliteBlob.Write(fileMemoryStreamData,
914 fileMemoryStreamData.Length, 928 fileMemoryStreamData.Length,
915 0); 929 0);
916 } 930 }
917   931  
918 using (var sqliteBlob = 932 using (var sqliteBlob =
919 SQLiteBlob.Create(sqliteConnection, "main", 933 SQLiteBlob.Create(sqliteConnection, "main",
920 "Snapshots", 934 "Snapshots",
921 "Shot", 935 "Shot",
922 rowId, 936 rowId,
923 false)) 937 false))
924 { 938 {
925 var bitmapMemoryStreamData = 939 var bitmapMemoryStreamData =
926 bitmapMemoryStream.ToArray(); 940 bitmapMemoryStream.ToArray();
927   941  
928 sqliteBlob.Write(bitmapMemoryStreamData, 942 sqliteBlob.Write(bitmapMemoryStreamData,
929 bitmapMemoryStreamData.Length, 943 bitmapMemoryStreamData.Length,
930 0); 944 0);
931 } 945 }
932 } 946 }
933   947  
934 dbTransaction.Commit(); 948 dbTransaction.Commit();
935   949  
936 SnapshotCreate?.Invoke(this, 950 SnapshotCreate?.Invoke(this,
937 new SnapshotCreateSuccessEventArgs(name, time, path, color, 951 new SnapshotCreateSuccessEventArgs(name, time, path, color,
938 hashHex)); 952 hashHex));
939 } 953 }
940 } 954 }
941 } 955 }
942 } 956 }
943 } 957 }
944 } 958 }
945 } 959 }
946 } 960 }
947 catch (SQLiteException exception) 961 catch (SQLiteException exception)
948 { 962 {
949 dbTransaction.Rollback(); 963 dbTransaction.Rollback();
950   964  
951 if (exception.ResultCode != SQLiteErrorCode.Constraint) 965 if (exception.ResultCode != SQLiteErrorCode.Constraint)
952 { 966 {
953 SnapshotCreate?.Invoke(this, 967 SnapshotCreate?.Invoke(this,
954 new SnapshotCreateFailureEventArgs(name, path, color, exception)); 968 new SnapshotCreateFailureEventArgs(name, path, color, exception));
955 } 969 }
956   970  
957 throw; 971 throw;
958 } 972 }
959 catch (Exception exception) 973 catch (Exception exception)
960 { 974 {
961 dbTransaction.Rollback(); 975 dbTransaction.Rollback();
962   976  
963 SnapshotCreate?.Invoke(this, 977 SnapshotCreate?.Invoke(this,
964 new SnapshotCreateFailureEventArgs(name, path, color, exception)); 978 new SnapshotCreateFailureEventArgs(name, path, color, exception));
965   979  
966 throw; 980 throw;
967 } 981 }
968 } 982 }
969 } 983 }
970 } 984 }
971 finally 985 finally
972 { 986 {
973 _databaseLock.Release(); 987 _databaseLock.Release();
974 } 988 }
975 } 989 }
976   990  
977 public async Task SaveFileAsync(string path, string hash, CancellationToken cancellationToken) 991 public async Task SaveFileAsync(string path, string hash, CancellationToken cancellationToken)
978 { 992 {
979 await _databaseLock.WaitAsync(cancellationToken); 993 await _databaseLock.WaitAsync(cancellationToken);
980 try 994 try
981 { 995 {
982 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 996 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
983 { 997 {
984 await sqliteConnection.OpenAsync(cancellationToken); 998 await sqliteConnection.OpenAsync(cancellationToken);
985   999  
986 // Insert the file change. 1000 // Insert the file change.
987 using (var sqliteCommand = 1001 using (var sqliteCommand =
988 new SQLiteCommand(RetrieveDataPathFromHashSql, sqliteConnection)) 1002 new SQLiteCommand(RetrieveDataPathFromHashSql, sqliteConnection))
989 { 1003 {
990 sqliteCommand.Parameters.AddRange(new[] 1004 sqliteCommand.Parameters.AddRange(new[]
991 { 1005 {
992 new SQLiteParameter("@hash", hash) 1006 new SQLiteParameter("@hash", hash)
993 }); 1007 });
994   1008  
995 sqliteCommand.Prepare(); 1009 sqliteCommand.Prepare();
996   1010  
997 using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken)) 1011 using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
998 { 1012 {
999 while (await sqlDataReader.ReadAsync(cancellationToken)) 1013 while (await sqlDataReader.ReadAsync(cancellationToken))
1000 { 1014 {
1001 // Create directories if they do not exist. 1015 // Create directories if they do not exist.
1002 var dir = Path.GetDirectoryName(path); 1016 var dir = Path.GetDirectoryName(path);
1003   1017  
1004 if (dir != null && !Directory.Exists(dir)) 1018 if (dir != null && !Directory.Exists(dir))
1005 { 1019 {
1006 Directory.CreateDirectory(dir); 1020 Directory.CreateDirectory(dir);
1007 } 1021 }
1008   1022  
1009 using (var readStream = sqlDataReader.GetStream(2)) 1023 using (var readStream = sqlDataReader.GetStream(2))
1010 { 1024 {
1011 using (var fileStream = 1025 using (var fileStream =
1012 await Miscellaneous.GetFileStream(path, FileMode.Create, FileAccess.Write, 1026 await Miscellaneous.GetFileStream(path, FileMode.Create, FileAccess.Write,
1013 FileShare.Write, 1027 FileShare.Write,
1014 cancellationToken)) 1028 cancellationToken))
1015 { 1029 {
1016 readStream.Position = 0L; 1030 readStream.Position = 0L;
1017   1031  
1018 using (var zipStream = new GZipStream(readStream, CompressionMode.Decompress)) 1032 using (var zipStream = new GZipStream(readStream, CompressionMode.Decompress))
1019 { 1033 {
1020 await zipStream.CopyToAsync(fileStream); 1034 await zipStream.CopyToAsync(fileStream);
1021 } 1035 }
1022 } 1036 }
1023 } 1037 }
1024 } 1038 }
1025 } 1039 }
1026 } 1040 }
1027 } 1041 }
1028 } 1042 }
1029 finally 1043 finally
1030 { 1044 {
1031 _databaseLock.Release(); 1045 _databaseLock.Release();
1032 } 1046 }
1033 } 1047 }
1034   1048  
1035 public async Task RevertFileAsync(string name, string hash, CancellationToken cancellationToken, bool atomic = true) 1049 public async Task RevertFileAsync(string name, string hash, CancellationToken cancellationToken, bool atomic = true)
1036 { 1050 {
1037 await _databaseLock.WaitAsync(cancellationToken); 1051 await _databaseLock.WaitAsync(cancellationToken);
1038 try 1052 try
1039 { 1053 {
1040 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 1054 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
1041 { 1055 {
1042 await sqliteConnection.OpenAsync(cancellationToken); 1056 await sqliteConnection.OpenAsync(cancellationToken);
1043   1057  
1044 // Insert the file change. 1058 // Insert the file change.
1045 using (var sqliteCommand = 1059 using (var sqliteCommand =
1046 new SQLiteCommand(RetrieveDataPathFromHashSql, sqliteConnection)) 1060 new SQLiteCommand(RetrieveDataPathFromHashSql, sqliteConnection))
1047 { 1061 {
1048 try 1062 try
1049 { 1063 {
1050 sqliteCommand.Parameters.AddRange(new[] 1064 sqliteCommand.Parameters.AddRange(new[]
1051 { 1065 {
1052 new SQLiteParameter("@hash", hash) 1066 new SQLiteParameter("@hash", hash)
1053 }); 1067 });
1054   1068  
1055 sqliteCommand.Prepare(); 1069 sqliteCommand.Prepare();
1056   1070  
1057 using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken)) 1071 using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
1058 { 1072 {
1059 while (await sqlDataReader.ReadAsync(cancellationToken)) 1073 while (await sqlDataReader.ReadAsync(cancellationToken))
1060 { 1074 {
1061 var path = (string)sqlDataReader["Path"]; 1075 var path = (string)sqlDataReader["Path"];
1062   1076  
1063 // Create directories if they do not exist. 1077 // Create directories if they do not exist.
1064 var dir = Path.GetDirectoryName(path); 1078 var dir = Path.GetDirectoryName(path);
1065   1079  
1066 if (dir != null && !Directory.Exists(dir)) 1080 if (dir != null && !Directory.Exists(dir))
1067 { 1081 {
1068 Directory.CreateDirectory(dir); 1082 Directory.CreateDirectory(dir);
1069 } 1083 }
1070   1084  
1071 switch (atomic) 1085 switch (atomic)
1072 { 1086 {
1073 case true: 1087 case true:
1074 // Atomic 1088 // Atomic
1075 var temp = Path.Combine(Path.GetDirectoryName(path), 1089 var temp = Path.Combine(Path.GetDirectoryName(path),
1076 $"{Path.GetFileName(path)}.temp"); 1090 $"{Path.GetFileName(path)}.temp");
1077   1091  
1078 using (var readStream = sqlDataReader.GetStream(2)) 1092 using (var readStream = sqlDataReader.GetStream(2))
1079 { 1093 {
1080 using (var fileStream = new FileStream(temp, FileMode.Create, 1094 using (var fileStream = new FileStream(temp, FileMode.Create,
1081 FileAccess.Write, 1095 FileAccess.Write,
1082 FileShare.None)) 1096 FileShare.None))
1083 { 1097 {
1084 using (var zipStream = 1098 using (var zipStream =
1085 new GZipStream(readStream, CompressionMode.Decompress)) 1099 new GZipStream(readStream, CompressionMode.Decompress))
1086 { 1100 {
1087 zipStream.CopyTo(fileStream); 1101 zipStream.CopyTo(fileStream);
1088 } 1102 }
1089 } 1103 }
1090 } 1104 }
1091   1105  
1092 try 1106 try
1093 { 1107 {
1094 File.Replace(temp, path, null, true); 1108 File.Replace(temp, path, null, true);
1095 } 1109 }
1096 catch 1110 catch
1097 { 1111 {
1098 try 1112 try
1099 { 1113 {
1100 File.Delete(temp); 1114 File.Delete(temp);
1101 } 1115 }
1102 catch (Exception exception) 1116 catch (Exception exception)
1103 { 1117 {
1104 // Suppress deletion errors of temporary file. 1118 // Suppress deletion errors of temporary file.
1105 Log.Warning(exception, "Could not delete temporary file.", temp); 1119 Log.Warning(exception, "Could not delete temporary file.", temp);
1106 } 1120 }
1107   1121  
1108 throw; 1122 throw;
1109 } 1123 }
1110   1124  
1111 break; 1125 break;
1112 default: 1126 default:
1113 // Asynchronous 1127 // Asynchronous
1114 using (var readStream = sqlDataReader.GetStream(2)) 1128 using (var readStream = sqlDataReader.GetStream(2))
1115 { 1129 {
1116 using (var fileStream = 1130 using (var fileStream =
1117 await Miscellaneous.GetFileStream(path, FileMode.Create, 1131 await Miscellaneous.GetFileStream(path, FileMode.Create,
1118 FileAccess.Write, 1132 FileAccess.Write,
1119 FileShare.Write, 1133 FileShare.Write,
1120 cancellationToken)) 1134 cancellationToken))
1121 { 1135 {
1122 readStream.Position = 0L; 1136 readStream.Position = 0L;
1123   1137  
1124 using (var zipStream = 1138 using (var zipStream =
1125 new GZipStream(readStream, CompressionMode.Decompress)) 1139 new GZipStream(readStream, CompressionMode.Decompress))
1126 { 1140 {
1127 await zipStream.CopyToAsync(fileStream); 1141 await zipStream.CopyToAsync(fileStream);
1128 } 1142 }
1129 } 1143 }
1130 } 1144 }
1131   1145  
1132   1146  
1133 break; 1147 break;
1134 } 1148 }
1135   1149  
1136 SnapshotRevert?.Invoke(this, new SnapshotRevertSuccessEventArgs(name)); 1150 SnapshotRevert?.Invoke(this, new SnapshotRevertSuccessEventArgs(name));
1137 } 1151 }
1138 } 1152 }
1139 } 1153 }
1140 catch 1154 catch
1141 { 1155 {
1142 SnapshotRevert?.Invoke(this, new SnapshotRevertFailureEventArgs(name)); 1156 SnapshotRevert?.Invoke(this, new SnapshotRevertFailureEventArgs(name));
1143   1157  
1144 throw; 1158 throw;
1145 } 1159 }
1146 } 1160 }
1147 } 1161 }
1148 } 1162 }
1149 finally 1163 finally
1150 { 1164 {
1151 _databaseLock.Release(); 1165 _databaseLock.Release();
1152 } 1166 }
1153 } 1167 }
1154   1168  
1155 public async Task RemoveFileFastAsync(IEnumerable<string> hashes, CancellationToken cancellationToken) 1169 public async Task RemoveFileFastAsync(IEnumerable<string> hashes, CancellationToken cancellationToken)
1156 { 1170 {
1157 await _databaseLock.WaitAsync(cancellationToken); 1171 await _databaseLock.WaitAsync(cancellationToken);
1158 try 1172 try
1159 { 1173 {
1160 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 1174 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
1161 { 1175 {
1162 await sqliteConnection.OpenAsync(cancellationToken); 1176 await sqliteConnection.OpenAsync(cancellationToken);
1163   1177  
1164 using (var dbTransaction = sqliteConnection.BeginTransaction()) 1178 using (var dbTransaction = sqliteConnection.BeginTransaction())
1165 { 1179 {
1166 try 1180 try
1167 { 1181 {
1168 var transactionCommands = new List<Task>(); 1182 var transactionCommands = new List<Task>();
1169   1183  
1170 foreach (var hash in hashes) 1184 foreach (var hash in hashes)
1171 { 1185 {
1172 // Insert the file change. 1186 // Insert the file change.
1173 using (var sqliteCommand = 1187 using (var sqliteCommand =
1174 new SQLiteCommand(RemoveSnapshotFromHashSql, sqliteConnection, dbTransaction)) 1188 new SQLiteCommand(RemoveSnapshotFromHashSql, sqliteConnection, dbTransaction))
1175 { 1189 {
1176 sqliteCommand.Parameters.AddRange(new[] 1190 sqliteCommand.Parameters.AddRange(new[]
1177 { 1191 {
1178 new SQLiteParameter("@hash", hash) 1192 new SQLiteParameter("@hash", hash)
1179 }); 1193 });
1180   1194  
1181 sqliteCommand.Prepare(); 1195 sqliteCommand.Prepare();
1182   1196  
1183 var command = sqliteCommand.ExecuteNonQueryAsync(cancellationToken); 1197 var command = sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
1184   1198  
1185 transactionCommands.Add(command); 1199 transactionCommands.Add(command);
1186 } 1200 }
1187 } 1201 }
1188   1202  
1189 await Task.WhenAll(transactionCommands); 1203 await Task.WhenAll(transactionCommands);
1190   1204  
1191 dbTransaction.Commit(); 1205 dbTransaction.Commit();
1192 } 1206 }
1193 catch 1207 catch
1194 { 1208 {
1195 dbTransaction.Rollback(); 1209 dbTransaction.Rollback();
1196   1210  
1197 throw; 1211 throw;
1198 } 1212 }
1199 } 1213 }
1200 } 1214 }
1201 } 1215 }
1202 finally 1216 finally
1203 { 1217 {
1204 _databaseLock.Release(); 1218 _databaseLock.Release();
1205 } 1219 }
1206 } 1220 }
1207   1221  
1208 public async Task RemoveFileAsync(string hash, CancellationToken cancellationToken) 1222 public async Task RemoveFileAsync(string hash, CancellationToken cancellationToken)
1209 { 1223 {
1210 await _databaseLock.WaitAsync(cancellationToken); 1224 await _databaseLock.WaitAsync(cancellationToken);
1211 try 1225 try
1212 { 1226 {
1213 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 1227 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
1214 { 1228 {
1215 await sqliteConnection.OpenAsync(cancellationToken); 1229 await sqliteConnection.OpenAsync(cancellationToken);
1216   1230  
1217 using (var dbTransaction = sqliteConnection.BeginTransaction()) 1231 using (var dbTransaction = sqliteConnection.BeginTransaction())
1218 { 1232 {
1219 // Insert the file change. 1233 // Insert the file change.
1220 using (var sqliteCommand = 1234 using (var sqliteCommand =
1221 new SQLiteCommand(RemoveSnapshotFromHashSql, sqliteConnection, dbTransaction)) 1235 new SQLiteCommand(RemoveSnapshotFromHashSql, sqliteConnection, dbTransaction))
1222 { 1236 {
1223 sqliteCommand.Parameters.AddRange(new[] 1237 sqliteCommand.Parameters.AddRange(new[]
1224 { 1238 {
1225 new SQLiteParameter("@hash", hash) 1239 new SQLiteParameter("@hash", hash)
1226 }); 1240 });
1227   1241  
1228 sqliteCommand.Prepare(); 1242 sqliteCommand.Prepare();
1229   1243  
1230 try 1244 try
1231 { 1245 {
1232 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken); 1246 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
1233   1247  
1234 dbTransaction.Commit(); 1248 dbTransaction.Commit();
1235 } 1249 }
1236 catch 1250 catch
1237 { 1251 {
1238 dbTransaction.Rollback(); 1252 dbTransaction.Rollback();
1239   1253  
1240 throw; 1254 throw;
1241 } 1255 }
1242 } 1256 }
1243 } 1257 }
1244 } 1258 }
1245 } 1259 }
1246 finally 1260 finally
1247 { 1261 {
1248 _databaseLock.Release(); 1262 _databaseLock.Release();
1249 } 1263 }
1250 } 1264 }
1251   1265  
1252 public async Task UpdateColorAsync(string hash, Color color, CancellationToken cancellationToken) 1266 public async Task UpdateColorAsync(string hash, Color color, CancellationToken cancellationToken)
1253 { 1267 {
1254 await _databaseLock.WaitAsync(cancellationToken); 1268 await _databaseLock.WaitAsync(cancellationToken);
1255   1269  
1256 try 1270 try
1257 { 1271 {
1258 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 1272 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
1259 { 1273 {
1260 await sqliteConnection.OpenAsync(cancellationToken); 1274 await sqliteConnection.OpenAsync(cancellationToken);
1261   1275  
1262 using (var dbTransaction = sqliteConnection.BeginTransaction()) 1276 using (var dbTransaction = sqliteConnection.BeginTransaction())
1263 { 1277 {
1264 // Insert the file change. 1278 // Insert the file change.
1265 using (var sqliteCommand = 1279 using (var sqliteCommand =
1266 new SQLiteCommand(UpdateColorFromHashSql, sqliteConnection, dbTransaction)) 1280 new SQLiteCommand(UpdateColorFromHashSql, sqliteConnection, dbTransaction))
1267 { 1281 {
1268 sqliteCommand.Parameters.AddRange(new[] 1282 sqliteCommand.Parameters.AddRange(new[]
1269 { 1283 {
1270 new SQLiteParameter("@hash", hash), 1284 new SQLiteParameter("@hash", hash)
1271 new SQLiteParameter("@color", color.ToArgb()) -  
1272 }); 1285 });
-   1286  
-   1287 var numeric = color.ToArgb();
-   1288 switch (numeric)
-   1289 {
-   1290 case 0:
-   1291 sqliteCommand.Parameters.Add(
-   1292 new SQLiteParameter("@color", DBNull.Value));
-   1293 break;
-   1294 default:
-   1295 sqliteCommand.Parameters.Add(
-   1296 new SQLiteParameter("@color", numeric));
-   1297 break;
-   1298 }
1273   1299  
1274 sqliteCommand.Prepare(); 1300 sqliteCommand.Prepare();
1275   1301  
1276 try 1302 try
1277 { 1303 {
1278 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken); 1304 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
1279   1305  
1280 dbTransaction.Commit(); 1306 dbTransaction.Commit();
1281 } 1307 }
1282 catch 1308 catch
1283 { 1309 {
1284 dbTransaction.Rollback(); 1310 dbTransaction.Rollback();
1285   1311  
1286 throw; 1312 throw;
1287 } 1313 }
1288 } 1314 }
1289 } 1315 }
1290 } 1316 }
1291 } 1317 }
1292 finally 1318 finally
1293 { 1319 {
1294 _databaseLock.Release(); 1320 _databaseLock.Release();
1295 } 1321 }
1296 } 1322 }
1297   1323  
1298 public async Task RemoveColorAsync(string hash, CancellationToken cancellationToken) 1324 public async Task RemoveColorAsync(string hash, CancellationToken cancellationToken)
1299 { 1325 {
1300 await _databaseLock.WaitAsync(cancellationToken); 1326 await _databaseLock.WaitAsync(cancellationToken);
1301 try 1327 try
1302 { 1328 {
1303 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 1329 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
1304 { 1330 {
1305 await sqliteConnection.OpenAsync(cancellationToken); 1331 await sqliteConnection.OpenAsync(cancellationToken);
1306   1332  
1307 using (var dbTransaction = sqliteConnection.BeginTransaction()) 1333 using (var dbTransaction = sqliteConnection.BeginTransaction())
1308 { 1334 {
1309 // Insert the file change. 1335 // Insert the file change.
1310 using (var sqliteCommand = 1336 using (var sqliteCommand =
1311 new SQLiteCommand(RemoveColorFromHashSql, sqliteConnection, dbTransaction)) 1337 new SQLiteCommand(RemoveColorFromHashSql, sqliteConnection, dbTransaction))
1312 { 1338 {
1313 sqliteCommand.Parameters.AddRange(new[] 1339 sqliteCommand.Parameters.AddRange(new[]
1314 { 1340 {
1315 new SQLiteParameter("@hash", hash) 1341 new SQLiteParameter("@hash", hash)
1316 }); 1342 });
1317   1343  
1318 sqliteCommand.Prepare(); 1344 sqliteCommand.Prepare();
1319   1345  
1320 try 1346 try
1321 { 1347 {
1322 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken); 1348 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
1323   1349  
1324 dbTransaction.Commit(); 1350 dbTransaction.Commit();
1325 } 1351 }
1326 catch 1352 catch
1327 { 1353 {
1328 dbTransaction.Rollback(); 1354 dbTransaction.Rollback();
1329   1355  
1330 throw; 1356 throw;
1331 } 1357 }
1332 } 1358 }
1333 } 1359 }
1334 } 1360 }
1335 } 1361 }
1336 finally 1362 finally
1337 { 1363 {
1338 _databaseLock.Release(); 1364 _databaseLock.Release();
1339 } 1365 }
1340 } 1366 }
1341   1367  
1342 public async Task<SnapshotPreview> RetrievePreviewAsync(string hash, CancellationToken cancellationToken) 1368 public async Task<SnapshotPreview> RetrievePreviewAsync(string hash, CancellationToken cancellationToken)
1343 { 1369 {
1344 await _databaseLock.WaitAsync(cancellationToken); 1370 await _databaseLock.WaitAsync(cancellationToken);
1345 try 1371 try
1346 { 1372 {
1347 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 1373 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
1348 { 1374 {
1349 await sqliteConnection.OpenAsync(cancellationToken); 1375 await sqliteConnection.OpenAsync(cancellationToken);
1350   1376  
1351 // Insert the file change. 1377 // Insert the file change.
1352 using (var sqliteCommand = new SQLiteCommand(RetrievePreviewFromHashSql, sqliteConnection)) 1378 using (var sqliteCommand = new SQLiteCommand(RetrievePreviewFromHashSql, sqliteConnection))
1353 { 1379 {
1354 sqliteCommand.Parameters.AddRange(new[] 1380 sqliteCommand.Parameters.AddRange(new[]
1355 { 1381 {
1356 new SQLiteParameter("@hash", hash) 1382 new SQLiteParameter("@hash", hash)
1357 }); 1383 });
1358   1384  
1359 var note = string.Empty; 1385 var note = string.Empty;
1360   1386  
1361 sqliteCommand.Prepare(); 1387 sqliteCommand.Prepare();
1362   1388  
1363 using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken)) 1389 using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
1364 { 1390 {
1365 while (await sqlDataReader.ReadAsync(cancellationToken)) 1391 while (await sqlDataReader.ReadAsync(cancellationToken))
1366 { 1392 {
1367 if (!(sqlDataReader["Note"] is DBNull)) 1393 if (!(sqlDataReader["Note"] is DBNull))
1368 { 1394 {
1369 note = (string)sqlDataReader["Note"]; 1395 note = (string)sqlDataReader["Note"];
1370 } 1396 }
1371   1397  
1372 Bitmap shot = null; 1398 Bitmap shot = null;
1373   1399  
1374 if (!(sqlDataReader["Shot"] is DBNull)) 1400 if (!(sqlDataReader["Shot"] is DBNull))
1375 { 1401 {
1376 var readStream = sqlDataReader.GetStream(2); 1402 var readStream = sqlDataReader.GetStream(2);
1377   1403  
1378 readStream.Position = 0L; 1404 readStream.Position = 0L;
1379   1405  
1380 try 1406 try
1381 { 1407 {
1382 using (var zipStream = new GZipStream(readStream, CompressionMode.Decompress)) 1408 using (var zipStream = new GZipStream(readStream, CompressionMode.Decompress))
1383 { 1409 {
1384 using (var image = Image.FromStream(zipStream)) 1410 using (var image = Image.FromStream(zipStream))
1385 { 1411 {
1386 shot = new Bitmap(image); 1412 shot = new Bitmap(image);
1387 } 1413 }
1388 } 1414 }
1389 } 1415 }
1390 catch (Exception exception) 1416 catch (Exception exception)
1391 { 1417 {
1392 Log.Error(exception, $"Could not retrieve image preview for snapshot {hash}."); 1418 Log.Error(exception, $"Could not retrieve image preview for snapshot {hash}.");
1393 return null; 1419 return null;
1394 } 1420 }
1395 } 1421 }
1396   1422  
1397 return new SnapshotPreview(hash, shot, note); 1423 return new SnapshotPreview(hash, shot, note);
1398 } 1424 }
1399   1425  
1400 return null; 1426 return null;
1401 } 1427 }
1402 } 1428 }
1403 } 1429 }
1404 } 1430 }
1405 finally 1431 finally
1406 { 1432 {
1407 _databaseLock.Release(); 1433 _databaseLock.Release();
1408 } 1434 }
1409 } 1435 }
1410   1436  
1411 public async Task<MemoryStream> RetrieveFileStreamAsync(string hash, CancellationToken cancellationToken) 1437 public async Task<MemoryStream> RetrieveFileStreamAsync(string hash, CancellationToken cancellationToken)
1412 { 1438 {
1413 await _databaseLock.WaitAsync(cancellationToken); 1439 await _databaseLock.WaitAsync(cancellationToken);
1414 try 1440 try
1415 { 1441 {
1416 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 1442 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
1417 { 1443 {
1418 await sqliteConnection.OpenAsync(cancellationToken); 1444 await sqliteConnection.OpenAsync(cancellationToken);
1419   1445  
1420 // Insert the file change. 1446 // Insert the file change.
1421 using (var sqliteCommand = new SQLiteCommand(RetrieveDataFromHashSql, sqliteConnection)) 1447 using (var sqliteCommand = new SQLiteCommand(RetrieveDataFromHashSql, sqliteConnection))
1422 { 1448 {
1423 sqliteCommand.Parameters.AddRange(new[] 1449 sqliteCommand.Parameters.AddRange(new[]
1424 { 1450 {
1425 new SQLiteParameter("@hash", hash) 1451 new SQLiteParameter("@hash", hash)
1426 }); 1452 });
1427   1453  
1428 sqliteCommand.Prepare(); 1454 sqliteCommand.Prepare();
1429   1455  
1430 using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken)) 1456 using (var sqlDataReader = await sqliteCommand.ExecuteReaderAsync(cancellationToken))
1431 { 1457 {
1432 while (await sqlDataReader.ReadAsync(cancellationToken)) 1458 while (await sqlDataReader.ReadAsync(cancellationToken))
1433 { 1459 {
1434 using (var readStream = sqlDataReader.GetStream(1)) 1460 using (var readStream = sqlDataReader.GetStream(1))
1435 { 1461 {
1436 using (var memoryStream = new MemoryStream()) 1462 using (var memoryStream = new MemoryStream())
1437 { 1463 {
1438 readStream.Position = 0L; 1464 readStream.Position = 0L;
1439   1465  
1440 await readStream.CopyToAsync(memoryStream); 1466 await readStream.CopyToAsync(memoryStream);
1441   1467  
1442 memoryStream.Position = 0L; 1468 memoryStream.Position = 0L;
1443   1469  
1444 using (var zipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) 1470 using (var zipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
1445 { 1471 {
1446 // Do not dispose the returned stream and leave it up to callers to dispose. 1472 // Do not dispose the returned stream and leave it up to callers to dispose.
1447 var outputStream = new MemoryStream(); 1473 var outputStream = new MemoryStream();
1448   1474  
1449 await zipStream.CopyToAsync(outputStream); 1475 await zipStream.CopyToAsync(outputStream);
1450   1476  
1451 outputStream.Position = 0L; 1477 outputStream.Position = 0L;
1452   1478  
1453 return outputStream; 1479 return outputStream;
1454 } 1480 }
1455 } 1481 }
1456 } 1482 }
1457 } 1483 }
1458   1484  
1459 return null; 1485 return null;
1460 } 1486 }
1461 } 1487 }
1462 } 1488 }
1463 } 1489 }
1464 finally 1490 finally
1465 { 1491 {
1466 _databaseLock.Release(); 1492 _databaseLock.Release();
1467 } 1493 }
1468 } 1494 }
1469   1495  
1470 public async Task RelocateFileAsync(string hash, string path, CancellationToken cancellationToken) 1496 public async Task RelocateFileAsync(string hash, string path, CancellationToken cancellationToken)
1471 { 1497 {
1472 await _databaseLock.WaitAsync(cancellationToken); 1498 await _databaseLock.WaitAsync(cancellationToken);
1473 try 1499 try
1474 { 1500 {
1475 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 1501 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
1476 { 1502 {
1477 await sqliteConnection.OpenAsync(cancellationToken); 1503 await sqliteConnection.OpenAsync(cancellationToken);
1478   1504  
1479 using (var dbTransaction = sqliteConnection.BeginTransaction()) 1505 using (var dbTransaction = sqliteConnection.BeginTransaction())
1480 { 1506 {
1481 // Insert the file change. 1507 // Insert the file change.
1482 using (var sqliteCommand = 1508 using (var sqliteCommand =
1483 new SQLiteCommand(RelocateFileFromHashSql, sqliteConnection, dbTransaction)) 1509 new SQLiteCommand(RelocateFileFromHashSql, sqliteConnection, dbTransaction))
1484 { 1510 {
1485 sqliteCommand.Parameters.AddRange(new[] 1511 sqliteCommand.Parameters.AddRange(new[]
1486 { 1512 {
1487 new SQLiteParameter("@hash", hash), 1513 new SQLiteParameter("@hash", hash),
1488 new SQLiteParameter("@path", path) 1514 new SQLiteParameter("@path", path)
1489 }); 1515 });
1490   1516  
1491 sqliteCommand.Prepare(); 1517 sqliteCommand.Prepare();
1492   1518  
1493 try 1519 try
1494 { 1520 {
1495 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken); 1521 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
1496   1522  
1497 dbTransaction.Commit(); 1523 dbTransaction.Commit();
1498 } 1524 }
1499 catch 1525 catch
1500 { 1526 {
1501 dbTransaction.Rollback(); 1527 dbTransaction.Rollback();
1502   1528  
1503 throw; 1529 throw;
1504 } 1530 }
1505 } 1531 }
1506 } 1532 }
1507 } 1533 }
1508 } 1534 }
1509   1535  
1510 finally 1536 finally
1511 { 1537 {
1512 _databaseLock.Release(); 1538 _databaseLock.Release();
1513 } 1539 }
1514 } 1540 }
1515   1541  
1516 public async Task UpdateNoteAsync(string hash, string note, CancellationToken cancellationToken) 1542 public async Task UpdateNoteAsync(string hash, string note, CancellationToken cancellationToken)
1517 { 1543 {
1518 await _databaseLock.WaitAsync(cancellationToken); 1544 await _databaseLock.WaitAsync(cancellationToken);
1519 try 1545 try
1520 { 1546 {
1521 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 1547 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
1522 { 1548 {
1523 await sqliteConnection.OpenAsync(cancellationToken); 1549 await sqliteConnection.OpenAsync(cancellationToken);
1524   1550  
1525 using (var dbTransaction = sqliteConnection.BeginTransaction()) 1551 using (var dbTransaction = sqliteConnection.BeginTransaction())
1526 { 1552 {
1527 // Insert the file change. 1553 // Insert the file change.
1528 using (var sqliteCommand = 1554 using (var sqliteCommand =
1529 new SQLiteCommand(UpdateNoteFromHashSql, sqliteConnection, dbTransaction)) 1555 new SQLiteCommand(UpdateNoteFromHashSql, sqliteConnection, dbTransaction))
1530 { 1556 {
1531 sqliteCommand.Parameters.AddRange(new[] 1557 sqliteCommand.Parameters.AddRange(new[]
1532 { 1558 {
1533 new SQLiteParameter("@hash", hash), 1559 new SQLiteParameter("@hash", hash),
1534 new SQLiteParameter("@note", note) 1560 new SQLiteParameter("@note", note)
1535 }); 1561 });
1536   1562  
1537 sqliteCommand.Prepare(); 1563 sqliteCommand.Prepare();
1538   1564  
1539 try 1565 try
1540 { 1566 {
1541 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken); 1567 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
1542   1568  
1543 dbTransaction.Commit(); 1569 dbTransaction.Commit();
1544   1570  
1545 SnapshotNoteUpdate?.Invoke(this, new SnapshotNoteUpdateSuccessEventArgs(note)); 1571 SnapshotNoteUpdate?.Invoke(this, new SnapshotNoteUpdateSuccessEventArgs(note));
1546 } 1572 }
1547 catch 1573 catch
1548 { 1574 {
1549 dbTransaction.Rollback(); 1575 dbTransaction.Rollback();
1550   1576  
1551 SnapshotNoteUpdate?.Invoke(this, new SnapshotNoteUpdateFailureEventArgs()); 1577 SnapshotNoteUpdate?.Invoke(this, new SnapshotNoteUpdateFailureEventArgs());
1552   1578  
1553 throw; 1579 throw;
1554 } 1580 }
1555 } 1581 }
1556 } 1582 }
1557 } 1583 }
1558 } 1584 }
1559 finally 1585 finally
1560 { 1586 {
1561 _databaseLock.Release(); 1587 _databaseLock.Release();
1562 } 1588 }
1563 } 1589 }
1564   1590  
1565 public async Task<string> UpdateFileAsync(string hash, byte[] data, CancellationToken cancellationToken) 1591 public async Task<string> UpdateFileAsync(string hash, byte[] data, CancellationToken cancellationToken)
1566 { 1592 {
1567 await _databaseLock.WaitAsync(cancellationToken); 1593 await _databaseLock.WaitAsync(cancellationToken);
1568 try 1594 try
1569 { 1595 {
1570 using (var dataMemoryStream = new MemoryStream(data)) 1596 using (var dataMemoryStream = new MemoryStream(data))
1571 { 1597 {
1572 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 1598 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
1573 { 1599 {
1574 await sqliteConnection.OpenAsync(cancellationToken); 1600 await sqliteConnection.OpenAsync(cancellationToken);
1575   1601  
1576 using (var dbTransaction = sqliteConnection.BeginTransaction()) 1602 using (var dbTransaction = sqliteConnection.BeginTransaction())
1577 { 1603 {
1578 try 1604 try
1579 { 1605 {
1580 using (var md5 = MD5.Create()) 1606 using (var md5 = MD5.Create())
1581 { 1607 {
1582 using (var hashMemoryStream = new MemoryStream()) 1608 using (var hashMemoryStream = new MemoryStream())
1583 { 1609 {
1584 dataMemoryStream.Position = 0L; 1610 dataMemoryStream.Position = 0L;
1585 await dataMemoryStream.CopyToAsync(hashMemoryStream); 1611 await dataMemoryStream.CopyToAsync(hashMemoryStream);
1586   1612  
1587 hashMemoryStream.Position = 0L; 1613 hashMemoryStream.Position = 0L;
1588 var recomputedHash = md5.ComputeHash(hashMemoryStream); 1614 var recomputedHash = md5.ComputeHash(hashMemoryStream);
1589 var hashHex = BitConverter.ToString(recomputedHash).Replace("-", "") 1615 var hashHex = BitConverter.ToString(recomputedHash).Replace("-", "")
1590 .ToLowerInvariant(); 1616 .ToLowerInvariant();
1591   1617  
1592 using (var fileMemoryStream = new MemoryStream()) 1618 using (var fileMemoryStream = new MemoryStream())
1593 { 1619 {
1594 using (var fileZipStream = 1620 using (var fileZipStream =
1595 new GZipStream(fileMemoryStream, CompressionMode.Compress, true)) 1621 new GZipStream(fileMemoryStream, CompressionMode.Compress, true))
1596 { 1622 {
1597 dataMemoryStream.Position = 0L; 1623 dataMemoryStream.Position = 0L;
1598 await dataMemoryStream.CopyToAsync(fileZipStream); 1624 await dataMemoryStream.CopyToAsync(fileZipStream);
1599 fileZipStream.Close(); 1625 fileZipStream.Close();
1600   1626  
1601 fileMemoryStream.Position = 0L; 1627 fileMemoryStream.Position = 0L;
1602   1628  
1603 // Insert the file change. 1629 // Insert the file change.
1604 using (var sqliteCommand = 1630 using (var sqliteCommand =
1605 new SQLiteCommand(UpdateFileSql, sqliteConnection, 1631 new SQLiteCommand(UpdateFileSql, sqliteConnection,
1606 dbTransaction)) 1632 dbTransaction))
1607 { 1633 {
1608 sqliteCommand.Parameters.AddRange(new[] 1634 sqliteCommand.Parameters.AddRange(new[]
1609 { 1635 {
1610 new SQLiteParameter("@dataLength", fileMemoryStream.Length), 1636 new SQLiteParameter("@dataLength", fileMemoryStream.Length),
1611 new SQLiteParameter("@recomputedHash", hashHex), 1637 new SQLiteParameter("@recomputedHash", hashHex),
1612 new SQLiteParameter("@hash", hash) 1638 new SQLiteParameter("@hash", hash)
1613 }); 1639 });
1614   1640  
1615 sqliteCommand.Prepare(); 1641 sqliteCommand.Prepare();
1616 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken); 1642 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
1617 } 1643 }
1618   1644  
1619 using (var sqliteCommand = 1645 using (var sqliteCommand =
1620 new SQLiteCommand(GetRowFromHashSql, sqliteConnection, 1646 new SQLiteCommand(GetRowFromHashSql, sqliteConnection,
1621 dbTransaction)) 1647 dbTransaction))
1622 { 1648 {
1623 sqliteCommand.Parameters.AddRange(new[] 1649 sqliteCommand.Parameters.AddRange(new[]
1624 { 1650 {
1625 new SQLiteParameter("@hash", hashHex) 1651 new SQLiteParameter("@hash", hashHex)
1626 }); 1652 });
1627   1653  
1628 sqliteCommand.Prepare(); 1654 sqliteCommand.Prepare();
1629   1655  
1630 using (var sqlDataReader = 1656 using (var sqlDataReader =
1631 await sqliteCommand.ExecuteReaderAsync(cancellationToken)) 1657 await sqliteCommand.ExecuteReaderAsync(cancellationToken))
1632 { 1658 {
1633 while (await sqlDataReader.ReadAsync(cancellationToken)) 1659 while (await sqlDataReader.ReadAsync(cancellationToken))
1634 { 1660 {
1635 if (sqlDataReader["id"] is long rowId) 1661 if (sqlDataReader["id"] is long rowId)
1636 { 1662 {
1637 using (var sqliteBlob = SQLiteBlob.Create( 1663 using (var sqliteBlob = SQLiteBlob.Create(
1638 sqliteConnection, 1664 sqliteConnection,
1639 "main", 1665 "main",
1640 "Snapshots", 1666 "Snapshots",
1641 "Data", 1667 "Data",
1642 rowId, false)) 1668 rowId, false))
1643 { 1669 {
1644 var fileMemoryStreamData = 1670 var fileMemoryStreamData =
1645 fileMemoryStream.ToArray(); 1671 fileMemoryStream.ToArray();
1646   1672  
1647 sqliteBlob.Write(fileMemoryStreamData, 1673 sqliteBlob.Write(fileMemoryStreamData,
1648 fileMemoryStreamData.Length, 1674 fileMemoryStreamData.Length,
1649 0); 1675 0);
1650 } 1676 }
1651 } 1677 }
1652 } 1678 }
1653 } 1679 }
1654 } 1680 }
1655   1681  
1656 dbTransaction.Commit(); 1682 dbTransaction.Commit();
1657   1683  
1658 SnapshotDataUpdate?.Invoke(this, 1684 SnapshotDataUpdate?.Invoke(this,
1659 new SnapshotDataUpdateSuccessEventArgs(hash, hashHex)); 1685 new SnapshotDataUpdateSuccessEventArgs(hash, hashHex));
1660   1686  
1661 return hashHex; 1687 return hashHex;
1662 } 1688 }
1663 } 1689 }
1664 } 1690 }
1665 } 1691 }
1666 } 1692 }
1667 catch 1693 catch
1668 { 1694 {
1669 dbTransaction.Rollback(); 1695 dbTransaction.Rollback();
1670   1696  
1671 SnapshotDataUpdate?.Invoke(this, new SnapshotDataUpdateFailureEventArgs(hash)); 1697 SnapshotDataUpdate?.Invoke(this, new SnapshotDataUpdateFailureEventArgs(hash));
1672   1698  
1673 throw; 1699 throw;
1674 } 1700 }
1675 } 1701 }
1676 } 1702 }
1677 } 1703 }
1678 } 1704 }
1679 finally 1705 finally
1680 { 1706 {
1681 _databaseLock.Release(); 1707 _databaseLock.Release();
1682 } 1708 }
1683 } 1709 }
1684   1710  
1685 public async Task UpdateHashAsync(string from, string to, CancellationToken cancellationToken) 1711 public async Task UpdateHashAsync(string from, string to, CancellationToken cancellationToken)
1686 { 1712 {
1687 await _databaseLock.WaitAsync(cancellationToken); 1713 await _databaseLock.WaitAsync(cancellationToken);
1688 try 1714 try
1689 { 1715 {
1690 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 1716 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
1691 { 1717 {
1692 await sqliteConnection.OpenAsync(cancellationToken); 1718 await sqliteConnection.OpenAsync(cancellationToken);
1693   1719  
1694 using (var dbTransaction = sqliteConnection.BeginTransaction()) 1720 using (var dbTransaction = sqliteConnection.BeginTransaction())
1695 { 1721 {
1696 // Insert the file change. 1722 // Insert the file change.
1697 using (var sqliteCommand = 1723 using (var sqliteCommand =
1698 new SQLiteCommand(UpdateHashFromHashSql, sqliteConnection, dbTransaction)) 1724 new SQLiteCommand(UpdateHashFromHashSql, sqliteConnection, dbTransaction))
1699 { 1725 {
1700 sqliteCommand.Parameters.AddRange(new[] 1726 sqliteCommand.Parameters.AddRange(new[]
1701 { 1727 {
1702 new SQLiteParameter("@from", from), 1728 new SQLiteParameter("@from", from),
1703 new SQLiteParameter("@to", to) 1729 new SQLiteParameter("@to", to)
1704 }); 1730 });
1705   1731  
1706 sqliteCommand.Prepare(); 1732 sqliteCommand.Prepare();
1707   1733  
1708 try 1734 try
1709 { 1735 {
1710 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken); 1736 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
1711   1737  
1712 dbTransaction.Commit(); 1738 dbTransaction.Commit();
1713 } 1739 }
1714 catch 1740 catch
1715 { 1741 {
1716 dbTransaction.Rollback(); 1742 dbTransaction.Rollback();
1717   1743  
1718 throw; 1744 throw;
1719 } 1745 }
1720 } 1746 }
1721 } 1747 }
1722 } 1748 }
1723 } 1749 }
1724 finally 1750 finally
1725 { 1751 {
1726 _databaseLock.Release(); 1752 _databaseLock.Release();
1727 } 1753 }
1728 } 1754 }
1729   1755  
1730 #endregion 1756 #endregion
1731   1757  
1732 #region Private Methods 1758 #region Private Methods
1733   1759  
1734 private async Task SetAutoVacuum(CancellationToken cancellationToken) 1760 private async Task SetAutoVacuum(CancellationToken cancellationToken)
1735 { 1761 {
1736 await _databaseLock.WaitAsync(cancellationToken); 1762 await _databaseLock.WaitAsync(cancellationToken);
1737 try 1763 try
1738 { 1764 {
1739 using (var sqliteConnection = 1765 using (var sqliteConnection =
1740 new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 1766 new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
1741 { 1767 {
1742 await sqliteConnection.OpenAsync(cancellationToken); 1768 await sqliteConnection.OpenAsync(cancellationToken);
1743   1769  
1744 // Set auto vacuum. 1770 // Set auto vacuum.
1745 using (var sqliteCommand = new SQLiteCommand(SetAutoVacuumSql, sqliteConnection)) 1771 using (var sqliteCommand = new SQLiteCommand(SetAutoVacuumSql, sqliteConnection))
1746 { 1772 {
1747 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken); 1773 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
1748 } 1774 }
1749 } 1775 }
1750 } 1776 }
1751 finally 1777 finally
1752 { 1778 {
1753 _databaseLock.Release(); 1779 _databaseLock.Release();
1754 } 1780 }
1755 } 1781 }
1756   1782  
1757 private async Task CreateDatabase(CancellationToken cancellationToken) 1783 private async Task CreateDatabase(CancellationToken cancellationToken)
1758 { 1784 {
1759 await _databaseLock.WaitAsync(cancellationToken); 1785 await _databaseLock.WaitAsync(cancellationToken);
1760 try 1786 try
1761 { 1787 {
1762 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString)) 1788 using (var sqliteConnection = new SQLiteConnection(_sqliteConnectionStringBuilder.ConnectionString))
1763 { 1789 {
1764 await sqliteConnection.OpenAsync(cancellationToken); 1790 await sqliteConnection.OpenAsync(cancellationToken);
1765   1791  
1766 using (var dbTransaction = sqliteConnection.BeginTransaction()) 1792 using (var dbTransaction = sqliteConnection.BeginTransaction())
1767 { 1793 {
1768 // Create the table if it does not exist. 1794 // Create the table if it does not exist.
1769 using (var sqliteCommand = new SQLiteCommand(CreateTableSql, sqliteConnection, dbTransaction)) 1795 using (var sqliteCommand = new SQLiteCommand(CreateTableSql, sqliteConnection, dbTransaction))
1770 { 1796 {
1771 try 1797 try
1772 { 1798 {
1773 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken); 1799 await sqliteCommand.ExecuteNonQueryAsync(cancellationToken);
1774   1800  
1775 dbTransaction.Commit(); 1801 dbTransaction.Commit();
1776 } 1802 }
1777 catch 1803 catch
1778 { 1804 {
1779 dbTransaction.Rollback(); 1805 dbTransaction.Rollback();
1780   1806  
1781 throw; 1807 throw;
1782 } 1808 }
1783 } 1809 }
1784 } 1810 }
1785 } 1811 }
1786 } 1812 }
1787 finally 1813 finally
1788 { 1814 {
1789 _databaseLock.Release(); 1815 _databaseLock.Release();
1790 } 1816 }
1791 } 1817 }
1792   1818  
1793 #endregion 1819 #endregion
1794   1820  
1795   1821  
1796 } 1822 }
1797 } 1823 }
1798   1824  
1799
Generated by GNU Enscript 1.6.5.90.
1825
Generated by GNU Enscript 1.6.5.90.
1800   1826  
1801   1827  
1802   1828