Horizon – Diff between revs 10 and 11

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