comparison src/libmlk-rpg/rpg/save.c @ 320:8f9937403749

misc: improve loading of data
author David Demelier <markand@malikania.fr>
date Fri, 01 Oct 2021 20:30:00 +0200
parents libmlk-rpg/rpg/save.c@b843eef4cc35
children 60a4f904da21
comparison
equal deleted inserted replaced
319:b843eef4cc35 320:8f9937403749
1 /*
2 * save.c -- save functions
3 *
4 * Copyright (c) 2020-2021 David Demelier <markand@malikania.fr>
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <assert.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23
24 #include <sqlite3.h>
25
26 #include <port/port.h>
27
28 #include <core/error.h>
29 #include <core/sys.h>
30 #include <core/util.h>
31
32 #include <assets/sql/init.h>
33 #include <assets/sql/property-get.h>
34 #include <assets/sql/property-remove.h>
35 #include <assets/sql/property-set.h>
36
37 #include "rpg_p.h"
38 #include "save.h"
39
40 #define SQL_BEGIN "BEGIN EXCLUSIVE TRANSACTION"
41 #define SQL_COMMIT "COMMIT"
42 #define SQL_ROLLBACK "ROLLBACK"
43
44 static int
45 exec(struct save *db, const char *sql)
46 {
47 if (sqlite3_exec(db->handle, sql, NULL, NULL, NULL) != SQLITE_OK)
48 return errorf("%s", sqlite3_errmsg(db->handle));
49
50 return 0;
51 }
52
53 static const char *
54 path(unsigned int idx)
55 {
56 return util_pathf("%s%u.db", sys_dir(SYS_DIR_SAVE), idx);
57 }
58
59 static int
60 execu(struct save *db, const unsigned char *sql)
61 {
62 return exec(db, (const char *)sql);
63 }
64
65 static int
66 verify(struct save *db)
67 {
68 struct {
69 time_t *date;
70 struct save_property prop;
71 } table[] = {
72 { .date = &db->created, { .key = "molko.create-date" } },
73 { .date = &db->updated, { .key = "molko.update-date" } },
74 };
75
76 /* Ensure create and update dates are present. */
77 for (size_t i = 0; i < UTIL_SIZE(table); ++i) {
78 if (save_get_property(db, &table[i].prop) < 0) {
79 sqlite3_close(db->handle);
80 return errorf(_("database not initialized correctly"));
81 }
82
83 *table[i].date = strtoull(table[i].prop.value, NULL, 10);
84 }
85
86 return 0;
87 }
88
89 static int
90 prepare(struct save *s, struct save_stmt *stmt, const char *sql, const char *args, va_list ap)
91 {
92 stmt->parent = s;
93 stmt->handle = NULL;
94
95 if (sqlite3_prepare(s->handle, sql, -1, (sqlite3_stmt **)&stmt->handle, NULL) != SQLITE_OK)
96 goto sqlite3_err;
97
98 for (int i = 1; args && *args; ++args) {
99 switch (*args) {
100 case 'i':
101 case 'u':
102 if (sqlite3_bind_int(stmt->handle, i++, va_arg(ap, int)) != SQLITE_OK)
103 return -1;
104 break;
105 case 's':
106 if (sqlite3_bind_text(stmt->handle, i++, va_arg(ap, const char *), -1, NULL) != SQLITE_OK)
107 return -1;
108 break;
109 case 't':
110 if (sqlite3_bind_int64(stmt->handle, i++, va_arg(ap, time_t)) != SQLITE_OK)
111 return -1;
112 break;
113 case ' ':
114 break;
115 default:
116 return errorf("invalid format: %c", *args);
117 }
118 }
119
120 return 0;
121
122 sqlite3_err:
123 return errorf("%s", sqlite3_errmsg(s->handle));
124 }
125
126 static int
127 extract(struct save_stmt *stmt, const char *args, va_list ap)
128 {
129 const int ncols = sqlite3_column_count(stmt->handle);
130
131 for (int c = 0; args && *args; ++args) {
132 if (c >= ncols)
133 return errorf("too many arguments");
134
135 /* TODO: type check. */
136 switch (*args) {
137 case 'i':
138 case 'u':
139 *va_arg(ap, int *) = sqlite3_column_int(stmt->handle, c++);
140 break;
141 case 's': {
142 char *str = va_arg(ap, char *);
143 size_t max = va_arg(ap, size_t);
144
145 strlcpy(str, (const char *)sqlite3_column_text(stmt->handle, c++), max);
146 break;
147 }
148 case 't':
149 *va_arg(ap, time_t *) = sqlite3_column_int64(stmt->handle, c++);
150 break;
151 case ' ':
152 break;
153 default:
154 return errorf("invalid format: %c", *args);
155 }
156 }
157
158 return 0;
159 }
160
161 int
162 save_open(struct save *db, unsigned int idx, enum save_mode mode)
163 {
164 assert(db);
165
166 return save_open_path(db, path(idx), mode);
167 }
168
169 int
170 save_open_path(struct save *db, const char *path, enum save_mode mode)
171 {
172 assert(db);
173 assert(path);
174
175 int flags = 0;
176
177 switch (mode) {
178 case SAVE_MODE_WRITE:
179 flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
180 break;
181 default:
182 flags = SQLITE_OPEN_READONLY;
183 break;
184 }
185
186 if (sqlite3_open_v2(path, (sqlite3**)&db->handle, flags, NULL) != SQLITE_OK)
187 goto sqlite3_err;
188
189 if (mode == SAVE_MODE_WRITE && execu(db, assets_sql_init) < 0)
190 goto sqlite3_err;
191
192 return verify(db);
193
194 sqlite3_err:
195 errorf("%s", sqlite3_errmsg(db->handle));
196 sqlite3_close(db->handle);
197
198 memset(db, 0, sizeof (*db));
199
200 return -1;
201 }
202
203 int
204 save_ok(const struct save *db)
205 {
206 assert(db);
207
208 return db && db->handle;
209 }
210
211 int
212 save_set_property(struct save *db, const struct save_property *prop)
213 {
214 assert(db);
215 assert(prop);
216
217 sqlite3_stmt *stmt = NULL;
218
219 if (exec(db, SQL_BEGIN) < 0)
220 return -1;
221 if (sqlite3_prepare(db->handle, (const char *)assets_sql_property_set, -1, &stmt, NULL) != SQLITE_OK)
222 goto sqlite3_err;
223 if (sqlite3_bind_text(stmt, 1, prop->key, -1, NULL) != SQLITE_OK ||
224 sqlite3_bind_text(stmt, 2, prop->value, -1, NULL) != SQLITE_OK)
225 goto sqlite3_err;
226 if (sqlite3_step(stmt) != SQLITE_DONE)
227 goto sqlite3_err;
228
229 sqlite3_finalize(stmt);
230
231 return exec(db, SQL_COMMIT);
232
233 sqlite3_err:
234 errorf("%s", sqlite3_errmsg(db->handle));
235
236 if (stmt)
237 sqlite3_finalize(stmt);
238
239 exec(db, SQL_ROLLBACK);
240
241 return -1;
242 }
243
244 int
245 save_get_property(struct save *db, struct save_property *prop)
246 {
247 assert(db);
248 assert(prop);
249
250 sqlite3_stmt *stmt = NULL;
251 int ret = 0;
252
253 if (sqlite3_prepare(db->handle, (const char *)assets_sql_property_get,
254 sizeof (assets_sql_property_get), &stmt, NULL) != SQLITE_OK)
255 goto sqlite3_err;
256 if (sqlite3_bind_text(stmt, 1, prop->key, -1, NULL) != SQLITE_OK)
257 goto sqlite3_err;
258
259 switch (sqlite3_step(stmt)) {
260 case SQLITE_DONE:
261 /* Not found. */
262 ret = errorf(_("property '%s' was not found"), prop->key);
263 break;
264 case SQLITE_ROW:
265 /* Found. */
266 strlcpy(prop->value, (const char *)sqlite3_column_text(stmt, 0),
267 sizeof (prop->value));
268 break;
269 default:
270 /* Error. */
271 goto sqlite3_err;
272 }
273
274 sqlite3_finalize(stmt);
275
276 return ret;
277
278 sqlite3_err:
279 errorf("%s", sqlite3_errmsg(db->handle));
280
281 if (stmt)
282 sqlite3_finalize(stmt);
283
284 return -1;
285 }
286
287 int
288 save_remove_property(struct save *db, const struct save_property *prop)
289 {
290 assert(db);
291 assert(prop);
292
293 sqlite3_stmt *stmt = NULL;
294
295 if (exec(db, SQL_BEGIN) < 0)
296 return -1;
297 if (sqlite3_prepare(db->handle, (const char *)assets_sql_property_remove,
298 sizeof (assets_sql_property_remove), &stmt, NULL) != SQLITE_OK)
299 goto sqlite3_err;
300 if (sqlite3_bind_text(stmt, 1, prop->key, -1, NULL) != SQLITE_OK)
301 goto sqlite3_err;
302 if (sqlite3_step(stmt) != SQLITE_DONE)
303 goto sqlite3_err;
304
305 sqlite3_finalize(stmt);
306
307 return exec(db, SQL_COMMIT);
308
309 sqlite3_err:
310 errorf("%s", sqlite3_errmsg(db->handle));
311
312 if (stmt)
313 sqlite3_finalize(stmt);
314
315 exec(db, SQL_ROLLBACK);
316
317 return -1;
318 }
319
320 int
321 save_exec(struct save *db, const char *sql, const char *args, ...)
322 {
323 assert(save_ok(db));
324 assert(sql && args);
325
326 struct save_stmt stmt;
327 int ret;
328 va_list ap;
329
330 va_start(ap, args);
331 ret = prepare(db, &stmt, sql, args, ap);
332 va_end(ap);
333
334 if (ret < 0)
335 return -1;
336
337 ret = save_stmt_next(&stmt, NULL) == 0;
338 save_stmt_finish(&stmt);
339
340 return ret;
341 }
342
343 void
344 save_finish(struct save *db)
345 {
346 assert(db);
347
348 if (db->handle)
349 sqlite3_close(db->handle);
350
351 memset(db, 0, sizeof (*db));
352 }
353
354 int
355 save_stmt_init(struct save *db, struct save_stmt *stmt, const char *sql, const char *args, ...)
356 {
357 assert(save_ok(db));
358 assert(stmt);
359 assert(args);
360
361 va_list ap;
362 int ret;
363
364 va_start(ap, args);
365 ret = prepare(db, stmt, sql, args, ap);
366 va_end(ap);
367
368 return ret;
369 }
370
371 int
372 save_stmt_next(struct save_stmt *stmt, const char *args, ...)
373 {
374 assert(stmt);
375
376 va_list ap;
377 int ret = -1;
378
379 switch (sqlite3_step(stmt->handle)) {
380 case SQLITE_ROW:
381 va_start(ap, args);
382
383 if (extract(stmt, args, ap))
384 ret = 1;
385
386 va_end(ap);
387 break;
388 case SQLITE_DONE:
389 ret = 0;
390 break;
391 default:
392 break;
393 }
394
395 return ret;
396 }
397
398 void
399 save_stmt_finish(struct save_stmt *stmt)
400 {
401 assert(stmt);
402
403 sqlite3_finalize(stmt->handle);
404 memset(stmt, 0, sizeof (*stmt));
405 }