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