Horizon – Diff between revs 25 and 26

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