Mercurial > molko
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 } |