Horizon – Blame information for rev 5

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