Horizon – Diff between revs 1 and 3

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