Horizon – Diff between revs 5 and 10

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