]> git.sesse.net Git - nageru/blob - futatabi/db.cpp
Use REPLACE instead of INSERT + DELETE for frame files.
[nageru] / futatabi / db.cpp
1 #include "db.h"
2
3 #include "frame.pb.h"
4
5 #include <string>
6 #include <unordered_set>
7
8 using namespace std;
9
10 DB::DB(const string &filename)
11 {
12         int ret = sqlite3_open(filename.c_str(), &db);
13         if (ret != SQLITE_OK) {
14                 fprintf(stderr, "%s: %s\n", filename.c_str(), sqlite3_errmsg(db));
15                 exit(1);
16         }
17
18         sqlite3_exec(db, R"(
19                 CREATE TABLE IF NOT EXISTS state (state BLOB);
20         )",
21                      nullptr, nullptr, nullptr);  // Ignore errors.
22
23         sqlite3_exec(db, R"(
24                 CREATE TABLE IF NOT EXISTS settings (settings BLOB);
25         )",
26                      nullptr, nullptr, nullptr);  // Ignore errors.
27
28         sqlite3_exec(db, R"(
29                 DROP TABLE file;
30         )",
31                      nullptr, nullptr, nullptr);  // Ignore errors.
32
33         sqlite3_exec(db, R"(
34                 DROP TABLE frame;
35         )",
36                      nullptr, nullptr, nullptr);  // Ignore errors.
37
38         sqlite3_exec(db, R"(
39                 CREATE TABLE IF NOT EXISTS filev2 (
40                         file INTEGER NOT NULL PRIMARY KEY,
41                         filename VARCHAR NOT NULL UNIQUE,
42                         size BIGINT NOT NULL,
43                         frames BLOB NOT NULL
44                 );
45         )",
46                      nullptr, nullptr, nullptr);  // Ignore errors.
47
48         sqlite3_exec(db, "PRAGMA journal_mode=WAL", nullptr, nullptr, nullptr);  // Ignore errors.
49         sqlite3_exec(db, "PRAGMA synchronous=NORMAL", nullptr, nullptr, nullptr);  // Ignore errors.
50 }
51
52 StateProto DB::get_state()
53 {
54         StateProto state;
55
56         sqlite3_stmt *stmt;
57         int ret = sqlite3_prepare_v2(db, "SELECT state FROM state", -1, &stmt, 0);
58         if (ret != SQLITE_OK) {
59                 fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db));
60                 exit(1);
61         }
62
63         ret = sqlite3_step(stmt);
64         if (ret == SQLITE_ROW) {
65                 bool ok = state.ParseFromArray(sqlite3_column_blob(stmt, 0), sqlite3_column_bytes(stmt, 0));
66                 if (!ok) {
67                         fprintf(stderr, "State in database is corrupted!\n");
68                         exit(1);
69                 }
70         } else if (ret != SQLITE_DONE) {
71                 fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db));
72                 exit(1);
73         }
74
75         ret = sqlite3_finalize(stmt);
76         if (ret != SQLITE_OK) {
77                 fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
78                 exit(1);
79         }
80
81         return state;
82 }
83
84 void DB::store_state(const StateProto &state)
85 {
86         string serialized;
87         state.SerializeToString(&serialized);
88
89         int ret = sqlite3_exec(db, "BEGIN", nullptr, nullptr, nullptr);
90         if (ret != SQLITE_OK) {
91                 fprintf(stderr, "BEGIN: %s\n", sqlite3_errmsg(db));
92                 exit(1);
93         }
94
95         ret = sqlite3_exec(db, "DELETE FROM state", nullptr, nullptr, nullptr);
96         if (ret != SQLITE_OK) {
97                 fprintf(stderr, "DELETE: %s\n", sqlite3_errmsg(db));
98                 exit(1);
99         }
100
101         sqlite3_stmt *stmt;
102         ret = sqlite3_prepare_v2(db, "INSERT INTO state VALUES (?)", -1, &stmt, 0);
103         if (ret != SQLITE_OK) {
104                 fprintf(stderr, "INSERT prepare: %s\n", sqlite3_errmsg(db));
105                 exit(1);
106         }
107
108         sqlite3_bind_blob(stmt, 1, serialized.data(), serialized.size(), SQLITE_STATIC);
109
110         ret = sqlite3_step(stmt);
111         if (ret == SQLITE_ROW) {
112                 fprintf(stderr, "INSERT step: %s\n", sqlite3_errmsg(db));
113                 exit(1);
114         }
115
116         ret = sqlite3_finalize(stmt);
117         if (ret != SQLITE_OK) {
118                 fprintf(stderr, "INSERT finalize: %s\n", sqlite3_errmsg(db));
119                 exit(1);
120         }
121
122         ret = sqlite3_exec(db, "COMMIT", nullptr, nullptr, nullptr);
123         if (ret != SQLITE_OK) {
124                 fprintf(stderr, "COMMIT: %s\n", sqlite3_errmsg(db));
125                 exit(1);
126         }
127 }
128
129 SettingsProto DB::get_settings()
130 {
131         SettingsProto settings;
132
133         sqlite3_stmt *stmt;
134         int ret = sqlite3_prepare_v2(db, "SELECT settings FROM settings", -1, &stmt, 0);
135         if (ret != SQLITE_OK) {
136                 fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db));
137                 exit(1);
138         }
139
140         ret = sqlite3_step(stmt);
141         if (ret == SQLITE_ROW) {
142                 bool ok = settings.ParseFromArray(sqlite3_column_blob(stmt, 0), sqlite3_column_bytes(stmt, 0));
143                 if (!ok) {
144                         fprintf(stderr, "State in database is corrupted!\n");
145                         exit(1);
146                 }
147         } else if (ret != SQLITE_DONE) {
148                 fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db));
149                 exit(1);
150         }
151
152         ret = sqlite3_finalize(stmt);
153         if (ret != SQLITE_OK) {
154                 fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
155                 exit(1);
156         }
157
158         return settings;
159 }
160
161 void DB::store_settings(const SettingsProto &settings)
162 {
163         string serialized;
164         settings.SerializeToString(&serialized);
165
166         int ret = sqlite3_exec(db, "BEGIN", nullptr, nullptr, nullptr);
167         if (ret != SQLITE_OK) {
168                 fprintf(stderr, "BEGIN: %s\n", sqlite3_errmsg(db));
169                 exit(1);
170         }
171
172         ret = sqlite3_exec(db, "DELETE FROM settings", nullptr, nullptr, nullptr);
173         if (ret != SQLITE_OK) {
174                 fprintf(stderr, "DELETE: %s\n", sqlite3_errmsg(db));
175                 exit(1);
176         }
177
178         sqlite3_stmt *stmt;
179         ret = sqlite3_prepare_v2(db, "INSERT INTO settings VALUES (?)", -1, &stmt, 0);
180         if (ret != SQLITE_OK) {
181                 fprintf(stderr, "INSERT prepare: %s\n", sqlite3_errmsg(db));
182                 exit(1);
183         }
184
185         sqlite3_bind_blob(stmt, 1, serialized.data(), serialized.size(), SQLITE_STATIC);
186
187         ret = sqlite3_step(stmt);
188         if (ret == SQLITE_ROW) {
189                 fprintf(stderr, "INSERT step: %s\n", sqlite3_errmsg(db));
190                 exit(1);
191         }
192
193         ret = sqlite3_finalize(stmt);
194         if (ret != SQLITE_OK) {
195                 fprintf(stderr, "INSERT finalize: %s\n", sqlite3_errmsg(db));
196                 exit(1);
197         }
198
199         ret = sqlite3_exec(db, "COMMIT", nullptr, nullptr, nullptr);
200         if (ret != SQLITE_OK) {
201                 fprintf(stderr, "COMMIT: %s\n", sqlite3_errmsg(db));
202                 exit(1);
203         }
204 }
205
206 vector<DB::FrameOnDiskAndStreamIdx> DB::load_frame_file(const string &filename, size_t size, unsigned filename_idx)
207 {
208         FileContentsProto file_contents;
209
210         sqlite3_stmt *stmt;
211         int ret = sqlite3_prepare_v2(db, "SELECT frames FROM filev2 WHERE filename=? AND size=?", -1, &stmt, 0);
212         if (ret != SQLITE_OK) {
213                 fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db));
214                 exit(1);
215         }
216
217         sqlite3_bind_text(stmt, 1, filename.data(), filename.size(), SQLITE_STATIC);
218         sqlite3_bind_int64(stmt, 2, size);
219
220         ret = sqlite3_step(stmt);
221         if (ret == SQLITE_ROW) {
222                 bool ok = file_contents.ParseFromArray(sqlite3_column_blob(stmt, 0), sqlite3_column_bytes(stmt, 0));
223                 if (!ok) {
224                         fprintf(stderr, "Frame list in database is corrupted!\n");
225                         exit(1);
226                 }
227         } else if (ret != SQLITE_DONE) {
228                 fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db));
229                 exit(1);
230         }
231
232         ret = sqlite3_finalize(stmt);
233         if (ret != SQLITE_OK) {
234                 fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
235                 exit(1);
236         }
237
238         vector<FrameOnDiskAndStreamIdx> frames;
239         for (const StreamContentsProto &stream : file_contents.stream()) {
240                 FrameOnDiskAndStreamIdx frame;
241                 frame.stream_idx = stream.stream_idx();
242                 for (int i = 0; i < stream.pts_size(); ++i) {
243                         frame.frame.filename_idx = filename_idx;
244                         frame.frame.pts = stream.pts(i);
245                         frame.frame.offset = stream.offset(i);
246                         frame.frame.size = stream.file_size(i);
247                         frames.push_back(frame);
248                 }
249         }
250
251         return frames;
252 }
253
254 void DB::store_frame_file(const string &filename, size_t size, const vector<FrameOnDiskAndStreamIdx> &frames)
255 {
256         int ret = sqlite3_exec(db, "BEGIN", nullptr, nullptr, nullptr);
257         if (ret != SQLITE_OK) {
258                 fprintf(stderr, "BEGIN: %s\n", sqlite3_errmsg(db));
259                 exit(1);
260         }
261
262         // Delete any existing instances with this filename.
263         sqlite3_stmt *stmt;
264
265         // Create the protobuf blob for the new row.
266         FileContentsProto file_contents;
267         unordered_set<unsigned> seen_stream_idx;  // Usually only one.
268         for (const FrameOnDiskAndStreamIdx &frame : frames) {
269                 seen_stream_idx.insert(frame.stream_idx);
270         }
271         for (unsigned stream_idx : seen_stream_idx) {
272                 StreamContentsProto *stream = file_contents.add_stream();
273                 stream->set_stream_idx(stream_idx);
274                 stream->mutable_pts()->Reserve(frames.size());
275                 stream->mutable_offset()->Reserve(frames.size());
276                 stream->mutable_file_size()->Reserve(frames.size());
277                 for (const FrameOnDiskAndStreamIdx &frame : frames) {
278                         if (frame.stream_idx != stream_idx) {
279                                 continue;
280                         }
281                         stream->add_pts(frame.frame.pts);
282                         stream->add_offset(frame.frame.offset);
283                         stream->add_file_size(frame.frame.size);
284                 }
285         }
286         string serialized;
287         file_contents.SerializeToString(&serialized);
288
289         // Insert the new row.
290         ret = sqlite3_prepare_v2(db, "REPLACE INTO filev2 (filename, size, frames) VALUES (?, ?, ?)", -1, &stmt, 0);
291         if (ret != SQLITE_OK) {
292                 fprintf(stderr, "INSERT prepare: %s\n", sqlite3_errmsg(db));
293                 exit(1);
294         }
295
296         sqlite3_bind_text(stmt, 1, filename.data(), filename.size(), SQLITE_STATIC);
297         sqlite3_bind_int64(stmt, 2, size);
298         sqlite3_bind_blob(stmt, 3, serialized.data(), serialized.size(), SQLITE_STATIC);
299
300         ret = sqlite3_step(stmt);
301         if (ret == SQLITE_ROW) {
302                 fprintf(stderr, "INSERT step: %s\n", sqlite3_errmsg(db));
303                 exit(1);
304         }
305
306         ret = sqlite3_finalize(stmt);
307         if (ret != SQLITE_OK) {
308                 fprintf(stderr, "INSERT finalize: %s\n", sqlite3_errmsg(db));
309                 exit(1);
310         }
311
312         // Commit.
313         ret = sqlite3_exec(db, "COMMIT", nullptr, nullptr, nullptr);
314         if (ret != SQLITE_OK) {
315                 fprintf(stderr, "COMMIT: %s\n", sqlite3_errmsg(db));
316                 exit(1);
317         }
318 }
319
320 void DB::clean_unused_frame_files(const vector<string> &used_filenames)
321 {
322         int ret = sqlite3_exec(db, "BEGIN", nullptr, nullptr, nullptr);
323         if (ret != SQLITE_OK) {
324                 fprintf(stderr, "BEGIN: %s\n", sqlite3_errmsg(db));
325                 exit(1);
326         }
327
328         ret = sqlite3_exec(db, R"(
329                 CREATE TEMPORARY TABLE used_filenames ( filename VARCHAR NOT NULL PRIMARY KEY )
330         )",
331                            nullptr, nullptr, nullptr);
332
333         if (ret != SQLITE_OK) {
334                 fprintf(stderr, "CREATE TEMPORARY TABLE: %s\n", sqlite3_errmsg(db));
335                 exit(1);
336         }
337
338         // Insert the new rows.
339         sqlite3_stmt *stmt;
340         ret = sqlite3_prepare_v2(db, "INSERT INTO used_filenames (filename) VALUES (?)", -1, &stmt, 0);
341         if (ret != SQLITE_OK) {
342                 fprintf(stderr, "INSERT prepare: %s\n", sqlite3_errmsg(db));
343                 exit(1);
344         }
345
346         for (const string &filename : used_filenames) {
347                 sqlite3_bind_text(stmt, 1, filename.data(), filename.size(), SQLITE_STATIC);
348
349                 ret = sqlite3_step(stmt);
350                 if (ret == SQLITE_ROW) {
351                         fprintf(stderr, "INSERT step: %s\n", sqlite3_errmsg(db));
352                         exit(1);
353                 }
354
355                 ret = sqlite3_reset(stmt);
356                 if (ret == SQLITE_ROW) {
357                         fprintf(stderr, "INSERT reset: %s\n", sqlite3_errmsg(db));
358                         exit(1);
359                 }
360         }
361
362         ret = sqlite3_finalize(stmt);
363         if (ret != SQLITE_OK) {
364                 fprintf(stderr, "INSERT finalize: %s\n", sqlite3_errmsg(db));
365                 exit(1);
366         }
367
368         ret = sqlite3_exec(db, R"(
369                 DELETE FROM filev2 WHERE filename NOT IN ( SELECT filename FROM used_filenames )
370         )",
371                            nullptr, nullptr, nullptr);
372
373         if (ret != SQLITE_OK) {
374                 fprintf(stderr, "DELETE: %s\n", sqlite3_errmsg(db));
375                 exit(1);
376         }
377
378         ret = sqlite3_exec(db, R"(
379                 DROP TABLE used_filenames
380         )",
381                            nullptr, nullptr, nullptr);
382
383         if (ret != SQLITE_OK) {
384                 fprintf(stderr, "DROP TABLE: %s\n", sqlite3_errmsg(db));
385                 exit(1);
386         }
387
388         // Commit.
389         ret = sqlite3_exec(db, "COMMIT", nullptr, nullptr, nullptr);
390         if (ret != SQLITE_OK) {
391                 fprintf(stderr, "COMMIT: %s\n", sqlite3_errmsg(db));
392                 exit(1);
393         }
394 }