Mercurial > molko
changeset 84:a6c2067709ce
core: implement basic save routines, closes #2476 @2h
author | David Demelier <markand@malikania.fr> |
---|---|
date | Sat, 07 Mar 2020 17:06:01 +0100 |
parents | f5d3e469eb93 |
children | 34e91215ec5a |
files | .hgignore INSTALL.md Makefile src/core/save.c src/core/save.h src/core/sys.c src/core/sys.h tests/test-save.c |
diffstat | 8 files changed, 419 insertions(+), 10 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Sun Feb 16 18:27:42 2020 +0100 +++ b/.hgignore Sat Mar 07 17:06:01 2020 +0100 @@ -17,6 +17,7 @@ ^tests/test-error(\.exe)?$ ^tests/test-map(\.exe)?$ ^tests/test-script(\.exe)?$ +^tests/test-save(\.exe)?$ ^tools/molko-map(\.exe)?$ # doxygen stuff.
--- a/INSTALL.md Sun Feb 16 18:27:42 2020 +0100 +++ b/INSTALL.md Sat Mar 07 17:06:01 2020 +0100 @@ -6,8 +6,8 @@ Requirements ------------ -- C99 compliant compiler, -- POSIX system (make, ar, shell), +- C11 compliant compiler, +- POSIX system (make, ar, shell, some POSIX functions), - [Jansson][], JSON parsing library, - [SDL2][], Multimedia library, - [SDL2_image][], Image loading addon for SDL2,
--- a/Makefile Sun Feb 16 18:27:42 2020 +0100 +++ b/Makefile Sat Mar 07 17:06:01 2020 +0100 @@ -45,6 +45,7 @@ src/core/map_state.c \ src/core/message.c \ src/core/painter.c \ + src/core/save.c \ src/core/script.c \ src/core/sprite.c \ src/core/sys.c \ @@ -75,6 +76,7 @@ TESTS= tests/test-color.c \ tests/test-error.c \ tests/test-map.c \ + tests/test-save.c \ tests/test-script.c TESTS_INCS= -I extern/libgreatest -I src/core ${SDL_CFLAGS} TESTS_LIBS= ${LIB} ${SDL_LDFLAGS} ${LDFLAGS} @@ -88,7 +90,7 @@ DEFINES= -DPREFIX=\""${PREFIX}"\" \ -DBINDIR=\""${BINDIR}"\" \ -DSHAREDIR=\""${SHAREDIR}"\" -INCLUDES= -I src/core -I src/adventure +INCLUDES= -I extern/libsqlite -I src/core -I src/adventure .SUFFIXES: .SUFFIXES: .c .o @@ -101,7 +103,7 @@ ${CC} ${DEFINES} ${INCLUDES} ${SDL_CFLAGS} ${CFLAGS} -MMD -c $< -o $@ .c: - ${CC} ${TESTS_INCS} -o $@ ${CFLAGS} $< ${TESTS_LIBS} + ${CC} ${TESTS_INCS} -o $@ ${CFLAGS} $< ${TESTS_LIBS} ${SQLITE_LIB} ${SQLITE_OBJ}: ${SQLITE_SRC} ${CC} ${CFLAGS} ${SQLITE_FLAGS} -c ${SQLITE_SRC} -o $@ @@ -110,10 +112,10 @@ ${AR} -rc $@ ${SQLITE_OBJ} ${LIB}: ${CORE_OBJS} - ${AR} -rcs $@ ${CORE_OBJS} + ${AR} -rc $@ ${CORE_OBJS} ${PROG}: ${LIB} ${ADV_OBJS} ${SQLITE_LIB} - ${CC} -o $@ ${ADV_OBJS} ${LIB} ${SDL_LDFLAGS} ${LDFLAGS} + ${CC} -o $@ ${ADV_OBJS} ${LIB} ${SQLITE_LIB} ${SDL_LDFLAGS} ${LDFLAGS} ${TESTS_OBJS}: ${LIB}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/save.c Sat Mar 07 17:06:01 2020 +0100 @@ -0,0 +1,214 @@ +/* + * save.c -- save functions + * + * Copyright (c) 2020 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <assert.h> +#include <stdio.h> +#include <string.h> + +#include <sqlite3.h> + +#include "error.h" +#include "save.h" +#include "sys.h" + +static sqlite3 *db; + +static const char *sinit = + "BEGIN EXCLUSIVE TRANSACTION;" + "" + "CREATE TABLE IF NOT EXISTS property(" + " id INTEGER PRIMARY KEY AUTOINCREMENT," + " key TEXT NOT NULL UNIQUE," + " value TEXT NOT NULL" + ");" + "" + "COMMIT" +; + +static const char *sbegin = + "BEGIN EXCLUSIVE TRANSACTION" +; + +static const char *scommit = + "COMMIT" +; + +static const char *srollback = + "ROLLBACK" +; + +static const char *sset_property = + "INSERT OR REPLACE INTO property(" + " key," + " value" + ")" + "VALUES(" + " ?," + " ?" + ");" +; + +static const char *sget_property = + "SELECT value" + " FROM property" + " WHERE key = ?" +; + +static const char *sremove_property = + "DELETE" + " FROM property" + " WHERE key = ?" +; + +static bool +exec(const char *sql) +{ + if (sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK) + return error_printf("%s", sqlite3_errmsg(db)); + + return true; +} + +bool +save_open(unsigned int idx) +{ + return save_open_path(sys_savepath(idx)); +} + +bool +save_open_path(const char *path) +{ + assert(path); + + if (sqlite3_open(path, &db) != SQLITE_OK) + return error_printf("database open error: %s", sqlite3_errmsg(db)); + if (sqlite3_exec(db, sinit, NULL, NULL, NULL) != SQLITE_OK) + return error_printf("database init error: %s", sqlite3_errmsg(db)); + + return true; +} + +bool +save_set_property(const char *key, const char *value) +{ + assert(key); + assert(value && strlen(value) <= SAVE_PROPERTY_VALUE_MAX); + + sqlite3_stmt *stmt = NULL; + + if (!exec(sbegin)) + return false; + if (sqlite3_prepare(db, sset_property, -1, &stmt, NULL) != SQLITE_OK) + goto sqlite3_err; + if (sqlite3_bind_text(stmt, 1, key, -1, NULL) != SQLITE_OK || + sqlite3_bind_text(stmt, 2, value, -1, NULL) != SQLITE_OK) + goto sqlite3_err; + if (sqlite3_step(stmt) != SQLITE_DONE) + goto sqlite3_err; + + sqlite3_finalize(stmt); + + return exec(scommit); + +sqlite3_err: + if (stmt) { + sqlite3_finalize(stmt); + exec(srollback); + } + + return error_printf("%s", sqlite3_errmsg(db)); +} + +const char * +save_get_property(const char *key) +{ + assert(key); + + static char value[SAVE_PROPERTY_VALUE_MAX + 1]; + const char *ret = value; + sqlite3_stmt *stmt = NULL; + + memset(value, 0, sizeof (value)); + + if (sqlite3_prepare(db, sget_property, -1, &stmt, NULL) != SQLITE_OK) + goto sqlite3_err; + if (sqlite3_bind_text(stmt, 1, key, -1, NULL) != SQLITE_OK) + goto sqlite3_err; + + switch (sqlite3_step(stmt)) { + case SQLITE_DONE: + /* Not found. */ + ret = NULL; + break; + case SQLITE_ROW: + /* Found. */ + snprintf(value, sizeof (value), "%s", sqlite3_column_text(stmt, 0)); + break; + default: + /* Error. */ + goto sqlite3_err; + } + + sqlite3_finalize(stmt); + + return ret; + +sqlite3_err: + if (stmt) + sqlite3_finalize(stmt); + + error_printf("%s", sqlite3_errmsg(db)); + + return NULL; +} + +bool +save_remove_property(const char *key) +{ + assert(key); + + sqlite3_stmt *stmt = NULL; + + if (!exec(sbegin)) + return false; + if (sqlite3_prepare(db, sremove_property, -1, &stmt, NULL) != SQLITE_OK) + goto sqlite3_err; + if (sqlite3_bind_text(stmt, 1, key, -1, NULL) != SQLITE_OK) + goto sqlite3_err; + if (sqlite3_step(stmt) != SQLITE_DONE) + goto sqlite3_err; + + sqlite3_finalize(stmt); + + return exec(scommit); + +sqlite3_err: + if (stmt) + sqlite3_finalize(stmt); + + error_printf("%s", sqlite3_errmsg(db)); + + return false; +} + +void +save_finish(void) +{ + if (db) + sqlite3_close(db); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/save.h Sat Mar 07 17:06:01 2020 +0100 @@ -0,0 +1,95 @@ +/* + * save.h -- save functions + * + * Copyright (c) 2020 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef MOLKO_SAVE_H +#define MOLKO_SAVE_H + +/** + * \file save.h + * \brief Save functions. + */ + +#include <stdbool.h> + +/** + * \brief Max property value. + */ +#define SAVE_PROPERTY_VALUE_MAX 1024 + +/** + * Open a database by index. + * + * This function use the preferred path to store local files under the user + * home directory. The parameter idx specifies the save slot to use. + * + * \param idx the save slot + * \return false on error + */ +bool +save_open(unsigned int idx); + +/** + * Open the save slot specified by path. + * + * \pre path != NULL + * \param path the path to the save slot + * \return false on error + */ +bool +save_open_path(const char *path); + +/** + * Sets an arbitrary property. + * + * If the property already exists, replace it. + * + * \pre key != NULL + * \pre value != NULL && strlen(value) <= SAVE_PROPERTY_VALUE_MAX + * \param key the property key + * \param value the property value + */ +bool +save_set_property(const char *key, const char *value); + +/** + * Get a property. + * + * \pre key != NULL + * \param key the property key + * \return the key or NULL if not found + */ +const char * +save_get_property(const char *key); + +/** + * Remove a property. + * + * \pre key != NULL + * \param key the property key + * \return false on error + */ +bool +save_remove_property(const char *key); + +/** + * Close the save slot. + */ +void +save_finish(void); + +#endif /* !MOLKO_SAVE_H */
--- a/src/core/sys.c Sun Feb 16 18:27:42 2020 +0100 +++ b/src/core/sys.c Sat Mar 07 17:06:01 2020 +0100 @@ -19,6 +19,7 @@ #include <assert.h> #include <stdio.h> #include <stdlib.h> +#include <limits.h> #include <SDL.h> #include <SDL_image.h> @@ -59,7 +60,7 @@ static void determine(char path[], size_t pathlen) { - char localassets[FILENAME_MAX]; + char localassets[PATH_MAX]; char *base = SDL_GetBasePath(); DIR *dp; @@ -101,7 +102,8 @@ { #if defined(__MINGW64__) /* On MinGW buffering leads to painful debugging. */ - setvbuf(stdout, NULL, _IONBF, 0); + setbuf(stderr, NULL); + setbuf(stdout, NULL); #endif if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) @@ -119,7 +121,7 @@ const char * sys_datadir(void) { - static char path[1024]; + static char path[PATH_MAX]; if (path[0] == '\0') determine(path, sizeof (path)); @@ -143,7 +145,7 @@ const char * sys_datapathv(const char *fmt, va_list ap) { - static char path[2048 + FILENAME_MAX]; + static char path[PATH_MAX]; char filename[FILENAME_MAX]; vsnprintf(filename, sizeof (filename), fmt, ap); @@ -152,6 +154,21 @@ return path; } +const char * +sys_savepath(unsigned int idx) +{ + static char path[PATH_MAX]; + char *pref; + + if ((pref = SDL_GetPrefPath("malikania", "molko"))) { + snprintf(path, sizeof (path), "%ssave-%u", idx); + SDL_free(pref); + } else + snprintf(path, sizeof (path), "save-%u", idx); + + return path; +} + void sys_close(void) {
--- a/src/core/sys.h Sun Feb 16 18:27:42 2020 +0100 +++ b/src/core/sys.h Sat Mar 07 17:06:01 2020 +0100 @@ -64,6 +64,17 @@ sys_datapathv(const char *fmt, va_list ap); /** + * Compute the path to the save file for the given game state. + * + * \param idx the save number + * \return the path to the database file + * \note This only compute the path, it does not check the presence of the file + * \post The returned value will never be NULL + */ +const char * +sys_savepath(unsigned int idx); + +/** * Close the system. */ void
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-save.c Sat Mar 07 17:06:01 2020 +0100 @@ -0,0 +1,69 @@ +/* + * test-save.c -- test save routines + * + * Copyright (c) 2020 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdio.h> + +#include <greatest.h> + +#include "save.h" + +#define NAME "test.db" + +static void +clean(void *data) +{ + (void)data; + + save_finish(); + remove(NAME); +} + +TEST +properties_simple(void) +{ + ASSERT(save_open_path(NAME)); + + /* Insert a new property 'initialized'. */ + ASSERT(save_set_property("initialized", "1")); + ASSERT_STR_EQ(save_get_property("initialized"), "1"); + + /* This must replace the existing value. */ + ASSERT(save_set_property("initialized", "2")); + ASSERT_STR_EQ(save_get_property("initialized"), "2"); + + ASSERT(save_remove_property("initialized")); + ASSERT(!save_get_property("initialized")); + + PASS(); +} + +SUITE(properties) +{ + SET_TEARDOWN(clean, NULL); + RUN_TEST(properties_simple); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + RUN_SUITE(properties); + GREATEST_MAIN_END(); +}