Horizon – Diff between revs 1 and 3
?pathlinks?
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 |
|
1502 |
|
|
1503 | |
1503 | |
|
1504 | |
1504 | |
|
1505 | |
1505 | |