Horizon – Diff between revs 23 and 25

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