Mercurial > molko
changeset 640:9850089c9671
core: resurrect VFS
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 27 Sep 2023 22:03:17 +0200 |
parents | 19d19f644b5e |
children | fcd124e513ea |
files | CMakeLists.txt cmake/FindZIP.cmake cmake/MlkOptions.cmake cmake/MlkOptions.install.cmake libmlk-core/CMakeLists.txt libmlk-core/libmlk-core-config.cmake libmlk-core/mlk/core/vfs-dir.c libmlk-core/mlk/core/vfs-dir.h libmlk-core/mlk/core/vfs-zip.c libmlk-core/mlk/core/vfs-zip.h libmlk-core/mlk/core/vfs.c libmlk-core/mlk/core/vfs.h libmlk-core/mlk/core/vfs_p.h libmlk-util/mlk/util/sysconfig.cmake.h tests/CMakeLists.txt tests/assets/vfs/data.zip tests/assets/vfs/directory/hello.txt tests/test-vfs-dir.c tests/test-vfs-zip.c |
diffstat | 19 files changed, 1046 insertions(+), 4 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Sat Sep 23 21:04:16 2023 +0200 +++ b/CMakeLists.txt Wed Sep 27 22:03:17 2023 +0200 @@ -85,6 +85,10 @@ find_package(Intl REQUIRED) endif () +if (MLK_WITH_ZIP) + find_package(ZIP REQUIRED) +endif () + add_subdirectory(extern/libsqlite) add_subdirectory(extern/libdt) add_subdirectory(extern/libutlist)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmake/FindZIP.cmake Wed Sep 27 22:03:17 2023 +0200 @@ -0,0 +1,66 @@ +# FindZIP +# ------- +# +# Find libzip library, this modules defines: +# +# ZIP_INCLUDE_DIRS, where to find zip.h +# ZIP_LIBRARIES, where to find library +# ZIP_FOUND, if it is found +# +# The following imported targets will be available: +# +# ZIP::ZIP, if found. + +find_package(ZLIB QUIET) + +find_path( + ZIP_INCLUDE_DIR + NAMES zip.h +) + +find_library( + ZIP_LIBRARY + NAMES zip libzip +) + +find_path( + ZIPCONF_INCLUDE_DIR + NAMES zipconf.h +) + +if (NOT ZIPCONF_INCLUDE_DIR) + # zipconf.h is sometimes directly in the include/ folder but on some systems + # like Windows, it is installed in the lib/ directory. + get_filename_component(_ZIP_PRIVATE_LIBRARY "${ZIP_LIBRARY}" DIRECTORY) + + find_path( + ZIPCONF_INCLUDE_DIR + NAMES zipconf.h + PATHS "${_ZIP_PRIVATE_LIBRARY}/libzip/include" + ) +endif () + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args( + ZIP + REQUIRED_VARS ZLIB_LIBRARIES ZLIB_INCLUDE_DIRS ZIP_LIBRARY ZIP_INCLUDE_DIR ZIPCONF_INCLUDE_DIR +) + +if (ZIP_FOUND) + set(ZIP_LIBRARIES ${ZIP_LIBRARY} ${ZLIB_LIBRARIES}) + set(ZIP_INCLUDE_DIRS ${ZIP_INCLUDE_DIR} ${ZIPCONF_INCLUDE_DIR} ${ZLIB_INCLUDE_DIRS}) + + if (NOT TARGET ZIP::ZIP) + add_library(ZIP::ZIP UNKNOWN IMPORTED) + set_target_properties( + ZIP::ZIP + PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${ZIP_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${ZIP_INCLUDE_DIRS}" + ) + endif () +endif () + +mark_as_advanced(ZIP_LIBRARY ZIP_INCLUDE_DIR ZIPCONF_INCLUDE_DIR)
--- a/cmake/MlkOptions.cmake Sat Sep 23 21:04:16 2023 +0200 +++ b/cmake/MlkOptions.cmake Wed Sep 27 22:03:17 2023 +0200 @@ -23,3 +23,4 @@ mlk_option(TESTS_GRAPHICAL On BOOL "Enable unit tests that requires graphical context") mlk_option(CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake" STRING "Destination for CMake files") mlk_option(JAVASCRIPT On BOOL "Enable Javascript bindings") +mlk_option(ZIP On BOOL "Enable zip file support in VFS")
--- a/cmake/MlkOptions.install.cmake Sat Sep 23 21:04:16 2023 +0200 +++ b/cmake/MlkOptions.install.cmake Wed Sep 27 22:03:17 2023 +0200 @@ -18,3 +18,4 @@ set(MLK_WITH_NLS @MLK_WITH_NLS@) set(MLK_WITH_CMAKEDIR @MLK_WITH_CMAKEDIR@) +set(MLK_WITH_ZIP @MLK_WITH_ZIP@)
--- a/libmlk-core/CMakeLists.txt Sat Sep 23 21:04:16 2023 +0200 +++ b/libmlk-core/CMakeLists.txt Wed Sep 27 22:03:17 2023 +0200 @@ -53,6 +53,10 @@ ${libmlk-core_SOURCE_DIR}/mlk/core/texture.c ${libmlk-core_SOURCE_DIR}/mlk/core/trace.c ${libmlk-core_SOURCE_DIR}/mlk/core/util.c + ${libmlk-core_SOURCE_DIR}/mlk/core/vfs-dir.c + ${libmlk-core_SOURCE_DIR}/mlk/core/vfs-zip.c + ${libmlk-core_SOURCE_DIR}/mlk/core/vfs.c + ${libmlk-core_SOURCE_DIR}/mlk/core/vfs_p.h ${libmlk-core_SOURCE_DIR}/mlk/core/window.c ) @@ -89,6 +93,9 @@ ${libmlk-core_SOURCE_DIR}/mlk/core/texture_p.h ${libmlk-core_SOURCE_DIR}/mlk/core/trace.h ${libmlk-core_SOURCE_DIR}/mlk/core/util.h + ${libmlk-core_SOURCE_DIR}/mlk/core/vfs-dir.h + ${libmlk-core_SOURCE_DIR}/mlk/core/vfs-zip.h + ${libmlk-core_SOURCE_DIR}/mlk/core/vfs.h ${libmlk-core_SOURCE_DIR}/mlk/core/window.h ${libmlk-core_SOURCE_DIR}/mlk/core/window_p.h ) @@ -114,6 +121,10 @@ list(APPEND LIBRARIES Intl::Intl) endif () +if (MLK_WITH_ZIP) + list(APPEND LIBRARIES ZIP::ZIP) +endif () + mlk_library( NAME libmlk-core ASSETS ${ASSETS}
--- a/libmlk-core/libmlk-core-config.cmake Sat Sep 23 21:04:16 2023 +0200 +++ b/libmlk-core/libmlk-core-config.cmake Wed Sep 27 22:03:17 2023 +0200 @@ -20,6 +20,7 @@ find_dependency(libmlk-util) find_dependency(Intl) +find_dependency(ZIP) include("${CMAKE_CURRENT_LIST_DIR}/libmlk-core-targets.cmake") include("${CMAKE_CURRENT_LIST_DIR}/../mlk/MlkBcc.cmake")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmlk-core/mlk/core/vfs-dir.c Wed Sep 27 22:03:17 2023 +0200 @@ -0,0 +1,134 @@ +/* + * vfs-dir.c -- VFS subsystem for directories + * + * Copyright (c) 2020-2023 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 <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "alloc.h" +#include "err.h" +#include "util.h" +#include "vfs-dir.h" +#include "vfs.h" + +#define MLK_VFS_DIR_FILE(self) \ + MLK_CONTAINER_OF(self, struct mlk_vfs_dir_file, file) + +#define MLK_VFS_DIR(self) \ + MLK_CONTAINER_OF(self, struct mlk_vfs_dir, vfs) + +static inline void +normalize(char *path) +{ + size_t len = strlen(path); + + for (char *p = path; *p; ++p) + if (*p == '\\') + *p = '/'; + + while (len && path[len - 1] == '/') + path[--len] = 0; +} + +static size_t +file_read(struct mlk_vfs_file *self, void *buf, size_t bufsz) +{ + struct mlk_vfs_dir_file *file = MLK_VFS_DIR_FILE(self); + size_t rv; + + rv = fread(buf, 1, bufsz, file->handle); + + if (ferror(file->handle)) + return -1; + + return rv; +} + +static size_t +file_write(struct mlk_vfs_file *self, const void *buf, size_t bufsz) +{ + struct mlk_vfs_dir_file *file = MLK_VFS_DIR_FILE(self); + size_t rv; + + rv = fwrite(buf, 1, bufsz, file->handle); + + if (ferror(file->handle)) + return -1; + + return rv; +} + +static int +file_flush(struct mlk_vfs_file *self) +{ + struct mlk_vfs_dir_file *file = MLK_VFS_DIR_FILE(self); + + return fflush(file->handle) == EOF ? -1 : 0; +} + +static void +file_free(struct mlk_vfs_file *self) +{ + struct mlk_vfs_dir_file *file = MLK_VFS_DIR_FILE(self); + + fclose(file->handle); + mlk_alloc_free(file); +} + +static struct mlk_vfs_file * +vfs_open(struct mlk_vfs *self, const char *entry, const char *mode) +{ + struct mlk_vfs_dir *dir = MLK_VFS_DIR(self); + struct mlk_vfs_dir_file *file; + + file = mlk_alloc_new0(1, sizeof (*file)); + + snprintf(file->path, sizeof (file->path), "%s/%s", dir->path, entry); + + if (!(file->handle = fopen(file->path, mode))) { + mlk_errf("%s: %s", file->path, strerror(errno)); + mlk_alloc_free(file); + return NULL; + } + + file->file.read = file_read; + file->file.write = file_write; + file->file.flush = file_flush; + file->file.free = file_free; + + return &file->file; +} + +void +mlk_vfs_dir_init(struct mlk_vfs_dir *dir, const char *path) +{ + assert(dir); + assert(path); + + mlk_util_strlcpy(dir->path, path, sizeof (dir->path)); + + /* Remove terminator and switch to UNIX paths. */ + normalize(dir->path); + + dir->vfs.data = NULL; + dir->vfs.open = vfs_open; + dir->vfs.finish = NULL; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmlk-core/mlk/core/vfs-dir.h Wed Sep 27 22:03:17 2023 +0200 @@ -0,0 +1,58 @@ +/* + * vfs-dir.h -- VFS subsystem for directories + * + * Copyright (c) 2020-2023 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 MLK_CORE_VFS_DIR_H +#define MLK_CORE_VFS_DIR_H + +#include <mlk/util/util.h> + +#include "vfs.h" + +struct mlk_vfs_dir_file { + char path[MLK_PATH_MAX]; + + struct mlk_vfs_file file; + + /** \cond MLK_PRIVATE_DECLS */ + void *handle; + /** \endcond MLK_PRIVATE_DECLS */ +}; + +struct mlk_vfs_dir { + /** + * (read-only) + * + * Path to the directory. + */ + char path[MLK_PATH_MAX]; + + struct mlk_vfs vfs; +}; + +#if defined(__cplusplus) +extern "C" { +#endif + +void +mlk_vfs_dir_init(struct mlk_vfs_dir *dir, const char *path); + +#if defined(__cplusplus) +} +#endif + +#endif /* !MLK_CORE_VFS_DIR_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmlk-core/mlk/core/vfs-zip.c Wed Sep 27 22:03:17 2023 +0200 @@ -0,0 +1,143 @@ +/* + * vfs-zip.c -- VFS subsystem for zip archives + * + * Copyright (c) 2020-2022 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 "sysconfig.h" + +#if defined(MLK_WITH_ZIP) + +#include <assert.h> +#include <string.h> + +#include <zip.h> + +#include "alloc.h" +#include "err.h" +#include "util.h" +#include "vfs-zip.h" +#include "vfs.h" + +#define MLK_VFS_ZIP_FILE(self) \ + MLK_CONTAINER_OF(self, struct mlk_vfs_zip_file, file) + +#define MLK_VFS_ZIP(self) \ + MLK_CONTAINER_OF(self, struct mlk_vfs_zip, vfs) + +static inline int +mkflags(const char *mode) +{ + /* TODO: this should check for mutual exclusions. */ + int flags = 0; + + for (; *mode; ++mode) { + switch (*mode) { + case 'w': + flags |= ZIP_CREATE; + break; + case 'x': + flags |= ZIP_EXCL; + break; + case '+': + flags |= ZIP_TRUNCATE; + break; + case 'r': + flags |= ZIP_RDONLY; + break; + default: + break; + } + } + + return flags; +} + +static size_t +file_read(struct mlk_vfs_file *self, void *buf, size_t bufsz) +{ + struct mlk_vfs_zip_file *file = MLK_VFS_ZIP_FILE(self); + + return zip_fread(file->handle, buf, bufsz); +} + +static int +file_flush(struct mlk_vfs_file *self) +{ + (void)self; + + return 0; +} + +static void +file_free(struct mlk_vfs_file *self) +{ + struct mlk_vfs_zip_file *file = MLK_VFS_ZIP_FILE(self); + + zip_fclose(file->handle); + mlk_alloc_free(self); +} + +static struct mlk_vfs_file * +vfs_open(struct mlk_vfs *self, const char *entry, const char *mode) +{ + (void)mode; + + struct mlk_vfs_zip *zip = MLK_VFS_ZIP(self); + struct mlk_vfs_zip_file *file; + + file = mlk_alloc_new0(1, sizeof (*file)); + + if (!(file->handle = zip_fopen(zip->handle, entry, 0))) { + mlk_errf("unable to open file in archive"); + mlk_alloc_free(file); + return NULL; + } + + file->file.read = file_read; + file->file.flush = file_flush; + file->file.free = file_free; + + return &file->file; +} + +static void +vfs_finish(struct mlk_vfs *self) +{ + struct mlk_vfs_zip *zip = MLK_VFS_ZIP(self); + + zip_close(zip->handle); +} + +int +mlk_vfs_zip_init(struct mlk_vfs_zip *zip, const char *file, const char *mode) +{ + assert(zip); + assert(file); + assert(mode); + + if (!(zip->handle = zip_open(file, mkflags(mode), NULL))) { + mlk_errf("%s: unable to open zip file", file); + return -1; + } + + zip->vfs.data = NULL; + zip->vfs.open = vfs_open; + zip->vfs.finish = vfs_finish; + + return 0; +} + +#endif /* !MLK_WITH_ZIP */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmlk-core/mlk/core/vfs-zip.h Wed Sep 27 22:03:17 2023 +0200 @@ -0,0 +1,56 @@ +/* + * vfs-zip.h -- VFS subsystem for zip archives + * + * Copyright (c) 2020-2022 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 MLK_CORE_VFS_ZIP_H +#define MLK_CORE_VFS_ZIP_H + +#include "sysconfig.h" +#include "vfs.h" + +#if defined(MLK_WITH_ZIP) + +#if defined(__cplusplus) +extern "C" { +#endif + +struct mlk_vfs_zip_file { + struct mlk_vfs_file file; + + /** \cond MLK_PRIVATE_DECLS */ + void *handle; + /** \endcond MLK_PRIVATE_DECLS */ +}; + +struct mlk_vfs_zip { + struct mlk_vfs vfs; + + /** \cond MLK_PRIVATE_DECLS */ + void *handle; + /** \endcond MLK_PRIVATE_DECLS */ +}; + +int +mlk_vfs_zip_init(struct mlk_vfs_zip *zip, const char *file, const char *mode); + +#if defined(__cplusplus) +} +#endif + +#endif /* !MLK_WITH_ZIP */ + +#endif /* !MLK_CORE_VFS_ZIP_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmlk-core/mlk/core/vfs.c Wed Sep 27 22:03:17 2023 +0200 @@ -0,0 +1,147 @@ +/* + * vfs.c -- virtual file system abstraction + * + * Copyright (c) 2020-2023 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 <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "alloc.h" +#include "err.h" +#include "vfs.h" +#include "vfs_p.h" + +struct mlk_vfs_file * +mlk_vfs_open(struct mlk_vfs *vfs, + const char *entry, + const char *mode) +{ + assert(vfs); + assert(entry); + assert(mode); + + return vfs->open(vfs, entry, mode); +} + +void +mlk_vfs_finish(struct mlk_vfs *vfs) +{ + assert(vfs); + + if (vfs->finish) + vfs->finish(vfs); +} + +size_t +mlk_vfs_file_read(struct mlk_vfs_file *file, void *buf, size_t bufsz) +{ + assert(file); + assert(buf); + + return file->read(file, buf, bufsz); +} + +char * +mlk_vfs_file_aread(struct mlk_vfs_file *file, size_t *outlen) +{ + char data[BUFSIZ], *str; + size_t nr, len = 0, cap = 128; + + /* Initial allocation. */ + str = mlk_alloc_new0(cap, 1); + + while ((nr = mlk_vfs_file_read(file, data, sizeof (data))) > 0) { + if (nr >= cap - len) { + cap *= 2; + str = mlk_alloc_resize(str, cap); + } + + sprintf(&str[len], "%.*s", (int)nr, data); + len += nr; + } + + if (nr == (size_t)-1) { + mlk_alloc_free(str); + return NULL; + } + + if (outlen) + *outlen = len; + + return str; +} + +size_t +mlk_vfs_file_write(struct mlk_vfs_file *file, void *buf, size_t bufsz) +{ + assert(file); + assert(buf); + + return file->write(file, buf, bufsz); +} + +int +mlk_vfs_file_flush(struct mlk_vfs_file *file) +{ + assert(file); + + if (file->flush) + return file->flush(file); + + return 0; +} + +void +mlk_vfs_file_free(struct mlk_vfs_file *file) +{ + assert(file); + + if (file->free) + file->free(file); +} + +/* private */ + +static int +rw_vfs_file_close(SDL_RWops *context) +{ + free(context->hidden.mem.base); + free(context); + + return 0; +} + +SDL_RWops * +mlk__vfs_to_rw(struct mlk_vfs_file *file) +{ + SDL_RWops *ops; + char *data; + size_t datasz; + + if (!(data = mlk_vfs_file_aread(file, &datasz))) + return NULL; + if (!(ops = SDL_RWFromConstMem(data, datasz))) { + free(data); + return mlk_errf("%s", SDL_GetError()), NULL; + } + + ops->close = rw_vfs_file_close; + + return ops; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmlk-core/mlk/core/vfs.h Wed Sep 27 22:03:17 2023 +0200 @@ -0,0 +1,273 @@ +/* + * vfs.h -- virtual file system abstraction + * + * Copyright (c) 2020-2023 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 MLK_CORE_VFS_H +#define MLK_CORE_VFS_H + +/** + * \file mlk/core/vfs.h + * \brief Virtual file system abstraction. + * + * This module offers an abstract interface to load files and perform read/write + * operation on them. This can be useful for games that are designed to load + * large assets from compressed archives. + * + * The molko frameworks comes with two implementations: + * + * | module | support | remarks | + * |--------------------|-------------|------------------------------------| + * | mlk/core/vfs-dir.h | read, write | opens file relative to a directory | + * | mlk/core/vfs-zip.h | read | zip archive files extractor | + * + * ## Initialize a VFS + * + * To use this module, you must first open a VFS interface. It is usually implemented + * using the ::MLK_CONTAINER_OF macro. + * + * Example with mlk/core/vfs-dir.h + * + * ```c + * struct mlk_vfs_dir dir; + * + * mlk_vfs_directory_init(&dir, "/usr/share/mario"); + * ``` + * + * The abstract interface will be located in the `vfs` field for each + * implementation. + * + * ## Opening files + * + * Once your VFS module is initialized, you can call ::mlk_vfs_open to load an + * entry from it: + * + * ```c + * struct mlk_vfs_file *file; + * char content[1024]; + * size_t len; + * + * file = mlk_vfs_open(vfs, "block.png"); + * + * if (!file) + * handle_failure(); + * + * // The function does not append a NUL terminator. + * len = mlk_vfs_file_read(file, content, sizeof (content) - 1); + * + * if (len == (size_t)-1) + * handle_failure(); + * + * // Use content freely... + * ``` + * + * ## Cleaning up resources + * + * Opened files SHOULD be destroyed before the VFS itself because depending on + * the underlying implementation, it is possible that those opened files have + * direct references to the VFS module. + * + * ```c + * mlk_vfs_file_free(file); + * mlk_vfs_finish(&dir.vfs); + * ``` + */ + +#include <stddef.h> + +struct mlk_vfs_file; + +/** + * \struct mlk_vfs + * \brief Abstract VFS loader. + */ +struct mlk_vfs { + /** + * (read-write, borrowed, optional) + * + * Arbitrary user data for callbacks. + */ + void *data; + + /** + * (read-write) + * + * Open the file from the VFS. + * + * The mode argument must contain the following letters: + * + * - `+`: truncate file if exists + * - `r`: open for reading + * - `w`: open for writing + * - `x`: open exclusive mode + * + * \pre self != NULL + * \pre entry != NULL + * \pre mode != NULL + * \param self this vfs + * \param entry the entry filename + * \param mode open mode as described above + * \return a VFS file or NULL on failure + */ + struct mlk_vfs_file * (*open)(struct mlk_vfs *self, + const char *entry, + const char *mode); + + /** + * (read-write) + * + * Cleanup resources. + * + * \pre self != NULL + * \param self this vfs + */ + void (*finish)(struct mlk_vfs *self); +}; + +/** + * \struct mlk_vfs_file + * \brief Abstract VFS file. + */ +struct mlk_vfs_file { + /** + * (read-write, borrowed, optional) + * + * Arbitrary user data for callbacks. + */ + void *data; + + /** + * (read-write, optional) + * + * Attempt to read file entry into the buffer destination. The function + * can read less bytes than requested. + * + * If the function is NULL, an error is returned with an error string + * telling that the operation is not supported. + * + * \pre self != NULL + * \pre buf != NULL + * \param self this VFS file + * \param buf the buffer destination + * \param bufsz maximum buffer size to read + * \return the number of bytes read or -1 on error + */ + size_t (*read)(struct mlk_vfs_file *self, void *buf, size_t bufsz); + + /** + * (read-write, optional) + * + * Attempt to write file entry from the buffer source. The function can + * write less bytes than requested. + * + * If the function is NULL, an error is returned with an error string + * telling that the operation is not supported. + * + * \pre self != NULL + * \pre buf != NULL + * \param self this VFS file + * \param buf the buffer source + * \param bufsz the buffer length + * \return the number of bytes written or -1 on error + */ + size_t (*write)(struct mlk_vfs_file *self, const void *buf, size_t bufsz); + + /** + * (read-write, optional) + * + * Attempt to flush pending input/output on the underlying interface. + * + * If the function is NULL, it is considered success. + * + * \pre self != NULL + * \param self this VFS file + * \return 0 on success or -1 on error + */ + int (*flush)(struct mlk_vfs_file *self); + + /** + * (read-write, optional) + * + * Cleanup resources for this VFS file. + * + * \pre self != NULL + * \param self this VFS file + */ + void (*free)(struct mlk_vfs_file *self); +}; + +#if defined(__cplusplus) +extern "C" +#endif + +/** + * Wrapper of ::mlk_vfs::open if not NULL. + */ +struct mlk_vfs_file * +mlk_vfs_open(struct mlk_vfs *vfs, + const char *entry, + const char *mode); + +/** + * Wrapper of ::mlk_vfs::finish if not NULL. + */ +void +mlk_vfs_finish(struct mlk_vfs *vfs); + +/** + * Wrapper of ::mlk_vfs_file::read if not NULL. + */ +size_t +mlk_vfs_file_read(struct mlk_vfs_file *file, void *buf, size_t bufsz); + +/** + * Convenient function to read an entire file content using repeated calls to + * ::mlk_vfs_file_read function until end of file is reached. + * + * The returned string is dynamically allocated and must be free'd using + * ::mlk_alloc_free function. + * + * \pre file != NULL + * \param file this VFS file + * \param len pointer receiving the number of bytes read (can be NULL) + * \return the string content + */ +char * +mlk_vfs_file_aread(struct mlk_vfs_file *file, size_t *len); + +/** + * Wrapper of ::mlk_vfs::finish if not NULL. + */ +size_t +mlk_vfs_file_write(struct mlk_vfs_file *file, void *buf, size_t bufsz); + +/** + * Wrapper of ::mlk_vfs::finish if not NULL. + */ +int +mlk_vfs_file_flush(struct mlk_vfs_file *file); + +/** + * Wrapper of ::mlk_vfs::finish if not NULL. + */ +void +mlk_vfs_file_free(struct mlk_vfs_file *file); + +#if defined(__cplusplus) +} +#endif + +#endif /* MLK_CORE_VFS_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmlk-core/mlk/core/vfs_p.h Wed Sep 27 22:03:17 2023 +0200 @@ -0,0 +1,29 @@ +/* + * vfs.h -- (PRIVATE) virtual file system abstraction + * + * Copyright (c) 2020-2022 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 MLK_CORE_VFS_P_H +#define MLK_CORE_VFS_P_H + +#include <SDL.h> + +struct mlk_vfs_file; + +SDL_RWops * +mlk__vfs_to_rw(struct mlk_vfs_file *); + +#endif /* !MLK_CORE_VFS_P_H */
--- a/libmlk-util/mlk/util/sysconfig.cmake.h Sat Sep 23 21:04:16 2023 +0200 +++ b/libmlk-util/mlk/util/sysconfig.cmake.h Wed Sep 27 22:03:17 2023 +0200 @@ -25,6 +25,7 @@ #define MLK_LOCALEDIR "@CMAKE_INSTALL_LOCALEDIR@" #cmakedefine MLK_WITH_NLS +#cmakedefine MLK_WITH_ZIP #cmakedefine MLK_HAVE_PATH_MAX
--- a/tests/CMakeLists.txt Sat Sep 23 21:04:16 2023 +0200 +++ b/tests/CMakeLists.txt Wed Sep 27 22:03:17 2023 +0200 @@ -30,13 +30,15 @@ save-quest state util + vfs-dir ) +if (MLK_WITH_ZIP) + list(APPEND TESTS vfs-zip) +endif () + if (MLK_WITH_TESTS_GRAPHICAL) - list( - APPEND TESTS - tileset - ) + list(APPEND TESTS tileset) endif () foreach (t ${TESTS})
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/assets/vfs/directory/hello.txt Wed Sep 27 22:03:17 2023 +0200 @@ -0,0 +1,1 @@ +Hello World!
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-vfs-dir.c Wed Sep 27 22:03:17 2023 +0200 @@ -0,0 +1,58 @@ +/* + * test-vfs-directory.c -- test VFS directory + * + * Copyright (c) 2020-2022 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 <mlk/core/vfs-dir.h> + +#include <dt.h> + +static void +test_basics_read(void) +{ + struct mlk_vfs_dir dir; + struct mlk_vfs_file *file; + char data[256] = {}; + + mlk_vfs_dir_init(&dir, DIRECTORY "/vfs/directory"); + + DT_ASSERT(file = mlk_vfs_open(&dir.vfs, "hello.txt", "r")); + DT_EQ_UINT(mlk_vfs_file_read(file, data, sizeof (data)), 13U); + DT_EQ_STR(data, "Hello World!\n"); + + mlk_vfs_file_free(file); + mlk_vfs_finish(&dir.vfs); +} + +static void +test_error_notfound(void) +{ + struct mlk_vfs_dir dir; + + mlk_vfs_dir_init(&dir, DIRECTORY "/vfs/directory"); + + DT_ASSERT(!mlk_vfs_open(&dir.vfs, "notfound.txt", "r")); + + mlk_vfs_finish(&dir.vfs); +} + +int +main(void) +{ + DT_RUN(test_basics_read); + DT_RUN(test_error_notfound); + DT_SUMMARY(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-vfs-zip.c Wed Sep 27 22:03:17 2023 +0200 @@ -0,0 +1,56 @@ +/* + * test-zip-directory.c -- test VFS zip + * + * Copyright (c) 2020-2022 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 <mlk/core/vfs-zip.h> + +#include <dt.h> + +static void +test_basics_read(void) +{ + struct mlk_vfs_zip zip; + struct mlk_vfs_file *file; + char data[256] = {}; + + DT_EQ_INT(mlk_vfs_zip_init(&zip, DIRECTORY "/vfs/data.zip", "r"), 0); + DT_ASSERT(file = mlk_vfs_open(&zip.vfs, "texts/hello.txt", "r")); + DT_EQ_UINT(mlk_vfs_file_read(file, data, sizeof (data)), 21U); + DT_EQ_STR(data, "Hello from zip file!\n"); + + mlk_vfs_file_free(file); + mlk_vfs_finish(&zip.vfs); +} + +static void +test_error_notfound(void) +{ + struct mlk_vfs_zip zip; + + DT_EQ_INT(mlk_vfs_zip_init(&zip, DIRECTORY "/vfs/data.zip", "r"), 0); + DT_ASSERT(!mlk_vfs_open(&zip.vfs, "notfound.txt", "r")); + + mlk_vfs_finish(&zip.vfs); +} + +int +main(void) +{ + DT_RUN(test_basics_read); + DT_RUN(test_error_notfound); + DT_SUMMARY(); +}