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