From 42d9e1be663c35d22d419d61d345387d99103475 Mon Sep 17 00:00:00 2001 From: Relintai Date: Sat, 13 Jan 2024 11:54:15 +0100 Subject: [PATCH] Took the DirAccess and FileAccess implementations from pandemonium. Also kept a few helper methods from the old api. Removed tinydir. --- sfw/core/3rd_tinydir.h | 831 -------------------------------- sfw/core/dir_access.cpp | 871 ++++++++++++++++++++++++++++++---- sfw/core/dir_access.h | 126 ++++- sfw/core/file_access.cpp | 857 ++++++++++++++++++++++++++++++--- sfw/core/file_access.h | 189 +++++++- sfw/core/marshalls.h | 142 ++++++ tools/merger/sfw_core.cpp.inl | 2 - tools/merger/sfw_core.h.inl | 5 + tools/merger/sfw_full.cpp.inl | 2 - tools/merger/sfw_full.h.inl | 5 + 10 files changed, 2004 insertions(+), 1026 deletions(-) delete mode 100644 sfw/core/3rd_tinydir.h create mode 100644 sfw/core/marshalls.h diff --git a/sfw/core/3rd_tinydir.h b/sfw/core/3rd_tinydir.h deleted file mode 100644 index e08eb84..0000000 --- a/sfw/core/3rd_tinydir.h +++ /dev/null @@ -1,831 +0,0 @@ -/* -Copyright (c) 2013-2019, tinydir authors: -- Cong Xu -- Lautis Sun -- Baudouin Feildel -- Andargor -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#ifndef TINYDIR_H -#define TINYDIR_H - -#ifdef __cplusplus -extern "C" { -#endif - -#if ((defined _UNICODE) && !(defined UNICODE)) -#define UNICODE -#endif - -#if ((defined UNICODE) && !(defined _UNICODE)) -#define _UNICODE -#endif - -#include -#include -#include -#ifdef _MSC_VER -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -# include -# pragma warning(push) -# pragma warning (disable : 4996) -#else -# include -# include -# include -# include -#endif -#ifdef __MINGW32__ -# include -#endif - - -/* types */ - -/* Windows UNICODE wide character support */ -#if defined _MSC_VER || defined __MINGW32__ -# define _tinydir_char_t TCHAR -# define TINYDIR_STRING(s) _TEXT(s) -# define _tinydir_strlen _tcslen -# define _tinydir_strcpy _tcscpy -# define _tinydir_strcat _tcscat -# define _tinydir_strcmp _tcscmp -# define _tinydir_strrchr _tcsrchr -# define _tinydir_strncmp _tcsncmp -#else -# define _tinydir_char_t char -# define TINYDIR_STRING(s) s -# define _tinydir_strlen strlen -# define _tinydir_strcpy strcpy -# define _tinydir_strcat strcat -# define _tinydir_strcmp strcmp -# define _tinydir_strrchr strrchr -# define _tinydir_strncmp strncmp -#endif - -#if (defined _MSC_VER || defined __MINGW32__) -# include -# define _TINYDIR_PATH_MAX MAX_PATH -#elif defined __linux__ -# include -# ifdef PATH_MAX -# define _TINYDIR_PATH_MAX PATH_MAX -# endif -#elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) -# include -# if defined(BSD) -# include -# ifdef PATH_MAX -# define _TINYDIR_PATH_MAX PATH_MAX -# endif -# endif -#endif - -#ifndef _TINYDIR_PATH_MAX -#define _TINYDIR_PATH_MAX 4096 -#endif - -#ifdef _MSC_VER -/* extra chars for the "\\*" mask */ -# define _TINYDIR_PATH_EXTRA 2 -#else -# define _TINYDIR_PATH_EXTRA 0 -#endif - -#define _TINYDIR_FILENAME_MAX 256 - -#if (defined _MSC_VER || defined __MINGW32__) -#define _TINYDIR_DRIVE_MAX 3 -#endif - -#ifdef _MSC_VER -# define _TINYDIR_FUNC static __inline -#elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L -# define _TINYDIR_FUNC static __inline__ -#else -# define _TINYDIR_FUNC static inline -#endif - -/* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */ -#ifdef TINYDIR_USE_READDIR_R - -/* readdir_r is a POSIX-only function, and may not be available under various - * environments/settings, e.g. MinGW. Use readdir fallback */ -#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\ - _POSIX_SOURCE -# define _TINYDIR_HAS_READDIR_R -#endif -#if _POSIX_C_SOURCE >= 200112L -# define _TINYDIR_HAS_FPATHCONF -# include -#endif -#if _BSD_SOURCE || _SVID_SOURCE || \ - (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700) -# define _TINYDIR_HAS_DIRFD -# include -#endif -#if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\ - defined _PC_NAME_MAX -# define _TINYDIR_USE_FPATHCONF -#endif -#if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\ - !(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX) -# define _TINYDIR_USE_READDIR -#endif - -/* Use readdir by default */ -#else -# define _TINYDIR_USE_READDIR -#endif - -/* MINGW32 has two versions of dirent, ASCII and UNICODE*/ -#ifndef _MSC_VER -#if (defined __MINGW32__) && (defined _UNICODE) -#define _TINYDIR_DIR _WDIR -#define _tinydir_dirent _wdirent -#define _tinydir_opendir _wopendir -#define _tinydir_readdir _wreaddir -#define _tinydir_closedir _wclosedir -#else -#define _TINYDIR_DIR DIR -#define _tinydir_dirent dirent -#define _tinydir_opendir opendir -#define _tinydir_readdir readdir -#define _tinydir_closedir closedir -#endif -#endif - -/* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */ -#if defined(_TINYDIR_MALLOC) && defined(_TINYDIR_FREE) -#elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE) -#else -#error "Either define both alloc and free or none of them!" -#endif - -#if !defined(_TINYDIR_MALLOC) - #define _TINYDIR_MALLOC(_size) malloc(_size) - #define _TINYDIR_FREE(_ptr) free(_ptr) -#endif /* !defined(_TINYDIR_MALLOC) */ - -typedef struct tinydir_file -{ - _tinydir_char_t path[_TINYDIR_PATH_MAX]; - _tinydir_char_t name[_TINYDIR_FILENAME_MAX]; - _tinydir_char_t *extension; - int is_dir; - int is_reg; - -#ifndef _MSC_VER -#ifdef __MINGW32__ - struct _stat _s; -#else - struct stat _s; -#endif -#endif -} tinydir_file; - -typedef struct tinydir_dir -{ - _tinydir_char_t path[_TINYDIR_PATH_MAX]; - int has_next; - size_t n_files; - - tinydir_file *_files; -#ifdef _MSC_VER - HANDLE _h; - WIN32_FIND_DATA _f; -#else - _TINYDIR_DIR *_d; - struct _tinydir_dirent *_e; -#ifndef _TINYDIR_USE_READDIR - struct _tinydir_dirent *_ep; -#endif -#endif -} tinydir_dir; - - -/* declarations */ - -_TINYDIR_FUNC -int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path); -_TINYDIR_FUNC -int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path); -_TINYDIR_FUNC -void tinydir_close(tinydir_dir *dir); - -_TINYDIR_FUNC -int tinydir_next(tinydir_dir *dir); -_TINYDIR_FUNC -int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file); -_TINYDIR_FUNC -int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i); -_TINYDIR_FUNC -int tinydir_open_subdir_n(tinydir_dir *dir, size_t i); - -_TINYDIR_FUNC -int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path); -_TINYDIR_FUNC -void _tinydir_get_ext(tinydir_file *file); -_TINYDIR_FUNC -int _tinydir_file_cmp(const void *a, const void *b); -#ifndef _MSC_VER -#ifndef _TINYDIR_USE_READDIR -_TINYDIR_FUNC -size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp); -#endif -#endif - - -/* definitions*/ - -_TINYDIR_FUNC -int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path) -{ -#ifndef _MSC_VER -#ifndef _TINYDIR_USE_READDIR - int error; - int size; /* using int size */ -#endif -#else - _tinydir_char_t path_buf[_TINYDIR_PATH_MAX]; -#endif - _tinydir_char_t *pathp; - - if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0) - { - errno = EINVAL; - return -1; - } - if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) - { - errno = ENAMETOOLONG; - return -1; - } - - /* initialise dir */ - dir->_files = NULL; -#ifdef _MSC_VER - dir->_h = INVALID_HANDLE_VALUE; -#else - dir->_d = NULL; -#ifndef _TINYDIR_USE_READDIR - dir->_ep = NULL; -#endif -#endif - tinydir_close(dir); - - _tinydir_strcpy(dir->path, path); - /* Remove trailing slashes */ - pathp = &dir->path[_tinydir_strlen(dir->path) - 1]; - while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/'))) - { - *pathp = TINYDIR_STRING('\0'); - pathp++; - } -#ifdef _MSC_VER - _tinydir_strcpy(path_buf, dir->path); - _tinydir_strcat(path_buf, TINYDIR_STRING("\\*")); -#if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - dir->_h = FindFirstFileEx(path_buf, FindExInfoStandard, &dir->_f, FindExSearchNameMatch, NULL, 0); -#else - dir->_h = FindFirstFile(path_buf, &dir->_f); -#endif - if (dir->_h == INVALID_HANDLE_VALUE) - { - errno = ENOENT; -#else - dir->_d = _tinydir_opendir(path); - if (dir->_d == NULL) - { -#endif - goto bail; - } - - /* read first file */ - dir->has_next = 1; -#ifndef _MSC_VER -#ifdef _TINYDIR_USE_READDIR - dir->_e = _tinydir_readdir(dir->_d); -#else - /* allocate dirent buffer for readdir_r */ - size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */ - if (size == -1) return -1; - dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size); - if (dir->_ep == NULL) return -1; - - error = readdir_r(dir->_d, dir->_ep, &dir->_e); - if (error != 0) return -1; -#endif - if (dir->_e == NULL) - { - dir->has_next = 0; - } -#endif - - return 0; - -bail: - tinydir_close(dir); - return -1; -} - -_TINYDIR_FUNC -int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path) -{ - /* Count the number of files first, to pre-allocate the files array */ - size_t n_files = 0; - if (tinydir_open(dir, path) == -1) - { - return -1; - } - while (dir->has_next) - { - n_files++; - if (tinydir_next(dir) == -1) - { - goto bail; - } - } - tinydir_close(dir); - - if (n_files == 0 || tinydir_open(dir, path) == -1) - { - return -1; - } - - dir->n_files = 0; - dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files); - if (dir->_files == NULL) - { - goto bail; - } - while (dir->has_next) - { - tinydir_file *p_file; - dir->n_files++; - - p_file = &dir->_files[dir->n_files - 1]; - if (tinydir_readfile(dir, p_file) == -1) - { - goto bail; - } - - if (tinydir_next(dir) == -1) - { - goto bail; - } - - /* Just in case the number of files has changed between the first and - second reads, terminate without writing into unallocated memory */ - if (dir->n_files == n_files) - { - break; - } - } - - qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp); - - return 0; - -bail: - tinydir_close(dir); - return -1; -} - -_TINYDIR_FUNC -void tinydir_close(tinydir_dir *dir) -{ - if (dir == NULL) - { - return; - } - - memset(dir->path, 0, sizeof(dir->path)); - dir->has_next = 0; - dir->n_files = 0; - _TINYDIR_FREE(dir->_files); - dir->_files = NULL; -#ifdef _MSC_VER - if (dir->_h != INVALID_HANDLE_VALUE) - { - FindClose(dir->_h); - } - dir->_h = INVALID_HANDLE_VALUE; -#else - if (dir->_d) - { - _tinydir_closedir(dir->_d); - } - dir->_d = NULL; - dir->_e = NULL; -#ifndef _TINYDIR_USE_READDIR - _TINYDIR_FREE(dir->_ep); - dir->_ep = NULL; -#endif -#endif -} - -_TINYDIR_FUNC -int tinydir_next(tinydir_dir *dir) -{ - if (dir == NULL) - { - errno = EINVAL; - return -1; - } - if (!dir->has_next) - { - errno = ENOENT; - return -1; - } - -#ifdef _MSC_VER - if (FindNextFile(dir->_h, &dir->_f) == 0) -#else -#ifdef _TINYDIR_USE_READDIR - dir->_e = _tinydir_readdir(dir->_d); -#else - if (dir->_ep == NULL) - { - return -1; - } - if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0) - { - return -1; - } -#endif - if (dir->_e == NULL) -#endif - { - dir->has_next = 0; -#ifdef _MSC_VER - if (GetLastError() != ERROR_SUCCESS && - GetLastError() != ERROR_NO_MORE_FILES) - { - tinydir_close(dir); - errno = EIO; - return -1; - } -#endif - } - - return 0; -} - -_TINYDIR_FUNC -int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file) -{ - const _tinydir_char_t *filename; - if (dir == NULL || file == NULL) - { - errno = EINVAL; - return -1; - } -#ifdef _MSC_VER - if (dir->_h == INVALID_HANDLE_VALUE) -#else - if (dir->_e == NULL) -#endif - { - errno = ENOENT; - return -1; - } - filename = -#ifdef _MSC_VER - dir->_f.cFileName; -#else - dir->_e->d_name; -#endif - if (_tinydir_strlen(dir->path) + - _tinydir_strlen(filename) + 1 + _TINYDIR_PATH_EXTRA >= - _TINYDIR_PATH_MAX) - { - /* the path for the file will be too long */ - errno = ENAMETOOLONG; - return -1; - } - if (_tinydir_strlen(filename) >= _TINYDIR_FILENAME_MAX) - { - errno = ENAMETOOLONG; - return -1; - } - - _tinydir_strcpy(file->path, dir->path); - if (_tinydir_strcmp(dir->path, TINYDIR_STRING("/")) != 0) - _tinydir_strcat(file->path, TINYDIR_STRING("/")); - _tinydir_strcpy(file->name, filename); - _tinydir_strcat(file->path, filename); -#ifndef _MSC_VER -#ifdef __MINGW32__ - if (_tstat( -#elif (defined _BSD_SOURCE) || (defined _DEFAULT_SOURCE) \ - || ((defined _XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500)) \ - || ((defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L)) - if (lstat( -#else - if (stat( -#endif - file->path, &file->_s) == -1) - { - return -1; - } -#endif - _tinydir_get_ext(file); - - file->is_dir = -#ifdef _MSC_VER - !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); -#else - S_ISDIR(file->_s.st_mode); -#endif - file->is_reg = -#ifdef _MSC_VER - !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) || - ( - !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) && - !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && - !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) && -#ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM - !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) && -#endif -#ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA - !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) && -#endif - !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) && - !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)); -#else - S_ISREG(file->_s.st_mode); -#endif - - return 0; -} - -_TINYDIR_FUNC -int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i) -{ - if (dir == NULL || file == NULL) - { - errno = EINVAL; - return -1; - } - if (i >= dir->n_files) - { - errno = ENOENT; - return -1; - } - - memcpy(file, &dir->_files[i], sizeof(tinydir_file)); - _tinydir_get_ext(file); - - return 0; -} - -_TINYDIR_FUNC -int tinydir_open_subdir_n(tinydir_dir *dir, size_t i) -{ - _tinydir_char_t path[_TINYDIR_PATH_MAX]; - if (dir == NULL) - { - errno = EINVAL; - return -1; - } - if (i >= dir->n_files || !dir->_files[i].is_dir) - { - errno = ENOENT; - return -1; - } - - _tinydir_strcpy(path, dir->_files[i].path); - tinydir_close(dir); - if (tinydir_open_sorted(dir, path) == -1) - { - return -1; - } - - return 0; -} - -/* Open a single file given its path */ -_TINYDIR_FUNC -int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path) -{ - tinydir_dir dir; - int result = 0; - int found = 0; - _tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX]; - _tinydir_char_t file_name_buf[_TINYDIR_FILENAME_MAX]; - _tinydir_char_t *dir_name; - _tinydir_char_t *base_name; -#if (defined _MSC_VER || defined __MINGW32__) - _tinydir_char_t drive_buf[_TINYDIR_PATH_MAX]; - _tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX]; -#endif - - if (file == NULL || path == NULL || _tinydir_strlen(path) == 0) - { - errno = EINVAL; - return -1; - } - if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) - { - errno = ENAMETOOLONG; - return -1; - } - - /* Get the parent path */ -#if (defined _MSC_VER || defined __MINGW32__) -#if ((defined _MSC_VER) && (_MSC_VER >= 1400)) - errno = _tsplitpath_s( - path, - drive_buf, _TINYDIR_DRIVE_MAX, - dir_name_buf, _TINYDIR_FILENAME_MAX, - file_name_buf, _TINYDIR_FILENAME_MAX, - ext_buf, _TINYDIR_FILENAME_MAX); -#else - _tsplitpath( - path, - drive_buf, - dir_name_buf, - file_name_buf, - ext_buf); -#endif - - if (errno) - { - return -1; - } - -/* _splitpath_s not work fine with only filename and widechar support */ -#ifdef _UNICODE - if (drive_buf[0] == L'\xFEFE') - drive_buf[0] = '\0'; - if (dir_name_buf[0] == L'\xFEFE') - dir_name_buf[0] = '\0'; -#endif - - /* Emulate the behavior of dirname by returning "." for dir name if it's - empty */ - if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0') - { - _tinydir_strcpy(dir_name_buf, TINYDIR_STRING(".")); - } - /* Concatenate the drive letter and dir name to form full dir name */ - _tinydir_strcat(drive_buf, dir_name_buf); - dir_name = drive_buf; - /* Concatenate the file name and extension to form base name */ - _tinydir_strcat(file_name_buf, ext_buf); - base_name = file_name_buf; -#else - _tinydir_strcpy(dir_name_buf, path); - dir_name = dirname(dir_name_buf); - _tinydir_strcpy(file_name_buf, path); - base_name = basename(file_name_buf); -#endif - - /* Special case: if the path is a root dir, open the parent dir as the file */ -#if (defined _MSC_VER || defined __MINGW32__) - if (_tinydir_strlen(base_name) == 0) -#else - if ((_tinydir_strcmp(base_name, TINYDIR_STRING("/"))) == 0) -#endif - { - memset(file, 0, sizeof * file); - file->is_dir = 1; - file->is_reg = 0; - _tinydir_strcpy(file->path, dir_name); - file->extension = file->path + _tinydir_strlen(file->path); - return 0; - } - - /* Open the parent directory */ - if (tinydir_open(&dir, dir_name) == -1) - { - return -1; - } - - /* Read through the parent directory and look for the file */ - while (dir.has_next) - { - if (tinydir_readfile(&dir, file) == -1) - { - result = -1; - goto bail; - } - if (_tinydir_strcmp(file->name, base_name) == 0) - { - /* File found */ - found = 1; - break; - } - tinydir_next(&dir); - } - if (!found) - { - result = -1; - errno = ENOENT; - } - -bail: - tinydir_close(&dir); - return result; -} - -_TINYDIR_FUNC -void _tinydir_get_ext(tinydir_file *file) -{ - _tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.')); - if (period == NULL) - { - file->extension = &(file->name[_tinydir_strlen(file->name)]); - } - else - { - file->extension = period + 1; - } -} - -_TINYDIR_FUNC -int _tinydir_file_cmp(const void *a, const void *b) -{ - const tinydir_file *fa = (const tinydir_file *)a; - const tinydir_file *fb = (const tinydir_file *)b; - if (fa->is_dir != fb->is_dir) - { - return -(fa->is_dir - fb->is_dir); - } - return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX); -} - -#ifndef _MSC_VER -#ifndef _TINYDIR_USE_READDIR -/* -The following authored by Ben Hutchings -from https://womble.decadent.org.uk/readdir_r-advisory.html -*/ -/* Calculate the required buffer size (in bytes) for directory * -* entries read from the given directory handle. Return -1 if this * -* this cannot be done. * -* * -* This code does not trust values of NAME_MAX that are less than * -* 255, since some systems (including at least HP-UX) incorrectly * -* define it to be a smaller value. */ -_TINYDIR_FUNC -size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp) -{ - long name_max; - size_t name_end; - /* parameter may be unused */ - (void)dirp; - -#if defined _TINYDIR_USE_FPATHCONF - name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX); - if (name_max == -1) -#if defined(NAME_MAX) - name_max = (NAME_MAX > 255) ? NAME_MAX : 255; -#else - return (size_t)(-1); -#endif -#elif defined(NAME_MAX) - name_max = (NAME_MAX > 255) ? NAME_MAX : 255; -#else -#error "buffer size for readdir_r cannot be determined" -#endif - name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1; - return (name_end > sizeof(struct _tinydir_dirent) ? - name_end : sizeof(struct _tinydir_dirent)); -} -#endif -#endif - -#ifdef __cplusplus -} -#endif - -# if defined (_MSC_VER) -# pragma warning(pop) -# endif - -#endif diff --git a/sfw/core/dir_access.cpp b/sfw/core/dir_access.cpp index d1085f1..d29b795 100644 --- a/sfw/core/dir_access.cpp +++ b/sfw/core/dir_access.cpp @@ -2,144 +2,811 @@ //--STRIP #include "dir_access.h" -#include "3rd_tinydir.h" #include //--STRIP -Error DirAccess::open_dir(const String &path, bool skip_specials) { - if (_dir_open) { - return ERR_CANT_ACQUIRE_RESOURCE; - } +/*************************************************************************/ +/* dir_access.cpp */ +/* From https://github.com/Relintai/pandemonium_engine (MIT) */ +/*************************************************************************/ + +//--STRIP +#include "dir_access.h" + +#include "core/list.h" + +#include "core/file_access.h" +#include "core/memory.h" +#include "core/local_vector.h" +//--STRIP + +#if defined(_WIN64) || defined(_WIN32) +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_MNTENT +#include +#endif + +#endif + +#if defined(_WIN64) || defined(_WIN32) +#else + +Error DirAccess::list_dir_begin(bool skip_specials) { + list_dir_end(); //close any previous dir opening! _skip_specials = skip_specials; - if (tinydir_open(_dir, path.utf8().get_data()) == -1) { - return FAILED; + //char real_current_dir_name[2048]; //is this enough?! + //getcwd(real_current_dir_name,2048); + //chdir(current_path.utf8().get_data()); + dir_stream = opendir(current_dir.utf8().get_data()); + //chdir(real_current_dir_name); + if (!dir_stream) { + return ERR_CANT_OPEN; //error! } - _dir_open = true; - return OK; } -Error DirAccess::open_dir(const char *path, bool skip_specials) { - if (_dir_open) { - return ERR_CANT_ACQUIRE_RESOURCE; +bool DirAccess::file_exists(String p_file) { + GLOBAL_LOCK_FUNCTION + + if (p_file.is_rel_path()) { + p_file = current_dir.plus_file(p_file); } - _skip_specials = skip_specials; + struct stat flags; + bool success = (stat(p_file.utf8().get_data(), &flags) == 0); - if (tinydir_open(_dir, path) == -1) { - return FAILED; + if (success && S_ISDIR(flags.st_mode)) { + success = false; } - _dir_open = true; - - return OK; + return success; } -void DirAccess::close_dir() { - if (!_dir_open) { - return; +bool DirAccess::dir_exists(String p_dir) { + GLOBAL_LOCK_FUNCTION + + if (p_dir.is_rel_path()) { + p_dir = get_current_dir().plus_file(p_dir); } - tinydir_close(_dir); + struct stat flags; + bool success = (stat(p_dir.utf8().get_data(), &flags) == 0); - _dir_open = false; + return (success && S_ISDIR(flags.st_mode)); } -bool DirAccess::has_next() { - if (!_dir) { +uint64_t DirAccess::get_modified_time(String p_file) { + if (p_file.is_rel_path()) { + p_file = current_dir.plus_file(p_file); + } + + struct stat flags; + bool success = (stat(p_file.utf8().get_data(), &flags) == 0); + + if (success) { + return flags.st_mtime; + } else { + ERR_FAIL_V(0); + }; + return 0; +}; + +String DirAccess::get_next() { + if (!dir_stream) { + return ""; + } + + dirent *entry = readdir(dir_stream); + + if (entry == nullptr) { + list_dir_end(); + return ""; + } + + String fname = fix_unicode_name(entry->d_name); + + // Look at d_type to determine if the entry is a directory, unless + // its type is unknown (the file system does not support it) or if + // the type is a link, in that case we want to resolve the link to + // known if it points to a directory. stat() will resolve the link + // for us. + if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) { + String f = current_dir.plus_file(fname); + + struct stat flags; + if (stat(f.utf8().get_data(), &flags) == 0) { + _cisdir = S_ISDIR(flags.st_mode); + } else { + _cisdir = false; + } + } else { + _cisdir = (entry->d_type == DT_DIR); + } + + _cishidden = is_hidden(fname); + + _cisspecial = is_special(fname); + + if (_skip_specials && _cisspecial) { + // Should only happen 2 times max + return get_next(); + } + + return fname; +} + +bool DirAccess::current_is_dir() const { + return _cisdir; +} + +bool DirAccess::current_is_file() const { + return !_cisdir; +} + +bool DirAccess::current_is_special_dir() const { + return _cisspecial; +} + +bool DirAccess::current_is_hidden() const { + return _cishidden; +} + +void DirAccess::list_dir_end() { + if (dir_stream) { + closedir(dir_stream); + } + dir_stream = nullptr; + _cisdir = false; +} + +#if defined(HAVE_MNTENT) && defined(X11_ENABLED) +static bool _filter_drive(struct mntent *mnt) { + // Ignore devices that don't point to /dev + if (strncmp(mnt->mnt_fsname, "/dev", 4) != 0) { return false; } - return _dir->has_next; -} -bool DirAccess::read() { - _read_file_result = tinydir_readfile(_dir, _file); - - return _read_file_result != -1; -} -bool DirAccess::next() { - if (!_dir->has_next) { - return false; - } - - bool rres = read(); - while (!rres && _dir->has_next) { - tinydir_next(_dir); - rres = read(); - } - - if (!rres) { - return false; - } - - if (_dir->has_next) { - tinydir_next(_dir); - } - - if (_skip_specials && current_is_dir() && current_is_special_dir()) { - return next(); - } - - return true; -} - -bool DirAccess::current_is_ok() { - return _read_file_result == 01; -} -String DirAccess::current_get_name() { - return String(_file->name); -} -String DirAccess::current_get_path() { - return String(_file->path); -} -String DirAccess::current_get_extension() { - return String(_file->extension); -} -const char *DirAccess::current_get_name_cstr() { - return _file->name; -} -const char *DirAccess::current_get_path_cstr() { - return _file->path; -} -const char *DirAccess::current_get_extension_cstr() { - return _file->extension; -} -bool DirAccess::current_is_file() { - return !_file->is_dir; -} -bool DirAccess::current_is_dir() { - return _file->is_dir; -} -bool DirAccess::current_is_special_dir() { - if ((_file->name[0] == '.' && _file->name[1] == '\0') || (_file->name[0] == '.' && _file->name[1] == '.')) { + // Accept devices mounted at common locations + if (strncmp(mnt->mnt_dir, "/media", 6) == 0 || + strncmp(mnt->mnt_dir, "/mnt", 4) == 0 || + strncmp(mnt->mnt_dir, "/home", 5) == 0 || + strncmp(mnt->mnt_dir, "/run/media", 10) == 0) { return true; } + // Ignore everything else return false; } +#endif -bool DirAccess::is_dir_open() { - return _dir_open; +static void _get_drives(List *list) { + list->push_back("/"); + +#if defined(HAVE_MNTENT) && defined(X11_ENABLED) + // Check /etc/mtab for the list of mounted partitions + FILE *mtab = setmntent("/etc/mtab", "r"); + if (mtab) { + struct mntent mnt; + char strings[4096]; + + while (getmntent_r(mtab, &mnt, strings, sizeof(strings))) { + if (mnt.mnt_dir != nullptr && _filter_drive(&mnt)) { + // Avoid duplicates + if (!list->find(mnt.mnt_dir)) { + list->push_back(mnt.mnt_dir); + } + } + } + + endmntent(mtab); + } +#endif + + // Add $HOME + const char *home = getenv("HOME"); + if (home) { + // Only add if it's not a duplicate + if (!list->find(home)) { + list->push_back(home); + } + + // Check $HOME/.config/gtk-3.0/bookmarks + char path[1024]; + snprintf(path, 1024, "%s/.config/gtk-3.0/bookmarks", home); + FILE *fd = fopen(path, "r"); + if (fd) { + char string[1024]; + while (fgets(string, 1024, fd)) { + // Parse only file:// links + if (strncmp(string, "file://", 7) == 0) { + // Strip any unwanted edges on the strings and push_back if it's not a duplicate + String fpath = String(string + 7).strip_edges().split_spaces()[0].percent_decode(); + if (!list->find(fpath)) { + list->push_back(fpath); + } + } + } + + fclose(fd); + } + } + + list->sort(); } -bool DirAccess::is_dir_closed() { - return !_dir_open; + +int DirAccess::get_drive_count() { + List list; + _get_drives(&list); + + return list.size(); +} + +String DirAccess::get_drive(int p_drive) { + List list; + _get_drives(&list); + + ERR_FAIL_INDEX_V(p_drive, list.size(), ""); + + return list[p_drive]; +} + +int DirAccess::get_current_drive() { + int drive = 0; + int max_length = -1; + const String path = get_current_dir().to_lower(); + for (int i = 0; i < get_drive_count(); i++) { + const String d = get_drive(i).to_lower(); + if (max_length < d.length() && path.begins_with(d)) { + max_length = d.length(); + drive = i; + } + } + return drive; +} + +bool DirAccess::drives_are_shortcuts() { + return true; +} + +Error DirAccess::make_dir(String p_dir) { + GLOBAL_LOCK_FUNCTION + + if (p_dir.is_rel_path()) { + p_dir = get_current_dir().plus_file(p_dir); + } + + bool success = (mkdir(p_dir.utf8().get_data(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0); + int err = errno; + + if (success) { + return OK; + }; + + if (err == EEXIST) { + return ERR_ALREADY_EXISTS; + }; + + return ERR_CANT_CREATE; +} + +Error DirAccess::change_dir(String p_dir) { + GLOBAL_LOCK_FUNCTION + + // prev_dir is the directory we are changing out of + String prev_dir; + char real_current_dir_name[2048]; + ERR_FAIL_COND_V(getcwd(real_current_dir_name, 2048) == nullptr, ERR_BUG); + if (prev_dir.parse_utf8(real_current_dir_name)) { + prev_dir = real_current_dir_name; //no utf8, maybe latin? + } + + // try_dir is the directory we are trying to change into + String try_dir = ""; + if (p_dir.is_rel_path()) { + String next_dir = current_dir.plus_file(p_dir); + next_dir = next_dir.simplify_path(); + try_dir = next_dir; + } else { + try_dir = p_dir; + } + + bool worked = (chdir(try_dir.utf8().get_data()) == 0); // we can only give this utf8 + if (!worked) { + return ERR_INVALID_PARAMETER; + } + + String base; + if (base != String() && !try_dir.begins_with(base)) { + ERR_FAIL_COND_V(getcwd(real_current_dir_name, 2048) == nullptr, ERR_BUG); + String new_dir; + new_dir.parse_utf8(real_current_dir_name); + + if (!new_dir.begins_with(base)) { + try_dir = current_dir; //revert + } + } + + // the directory exists, so set current_dir to try_dir + current_dir = try_dir; + ERR_FAIL_COND_V(chdir(prev_dir.utf8().get_data()) != 0, ERR_BUG); + return OK; +} + +String DirAccess::get_current_dir() { + String base; + if (base != "") { + String bd = current_dir.replace_first(base, ""); + if (bd.begins_with("/")) { + return bd.substr(1, bd.length()); + } else { + return bd; + } + } + return current_dir; +} + +Error DirAccess::rename(String p_path, String p_new_path) { + if (p_path.is_rel_path()) { + p_path = get_current_dir().plus_file(p_path); + } + + if (p_new_path.is_rel_path()) { + p_new_path = get_current_dir().plus_file(p_new_path); + } + + return ::rename(p_path.utf8().get_data(), p_new_path.utf8().get_data()) == 0 ? OK : FAILED; +} + +Error DirAccess::remove(String p_path) { + if (p_path.is_rel_path()) { + p_path = get_current_dir().plus_file(p_path); + } + + struct stat flags; + if ((stat(p_path.utf8().get_data(), &flags) != 0)) { + return FAILED; + } + + if (S_ISDIR(flags.st_mode)) { + return ::rmdir(p_path.utf8().get_data()) == 0 ? OK : FAILED; + } else { + return ::unlink(p_path.utf8().get_data()) == 0 ? OK : FAILED; + } +} + +bool DirAccess::is_link(String p_file) { + if (p_file.is_rel_path()) { + p_file = get_current_dir().plus_file(p_file); + } + + struct stat flags; + if ((lstat(p_file.utf8().get_data(), &flags) != 0)) + return FAILED; + + return S_ISLNK(flags.st_mode); +} + +String DirAccess::read_link(String p_file) { + if (p_file.is_rel_path()) { + p_file = get_current_dir().plus_file(p_file); + } + + char buf[256]; + memset(buf, 0, 256); + ssize_t len = readlink(p_file.utf8().get_data(), buf, sizeof(buf)); + String link; + if (len > 0) { + link.parse_utf8(buf, len); + } + return link; +} + +Error DirAccess::create_link(String p_source, String p_target) { + if (p_target.is_rel_path()) + p_target = get_current_dir().plus_file(p_target); + + if (symlink(p_source.utf8().get_data(), p_target.utf8().get_data()) == 0) { + return OK; + } else { + return FAILED; + } +} + +uint64_t DirAccess::get_space_left() { +#ifndef NO_STATVFS + struct statvfs vfs; + if (statvfs(current_dir.utf8().get_data(), &vfs) != 0) { + return 0; + }; + + return (uint64_t)vfs.f_bavail * (uint64_t)vfs.f_frsize; +#else + // FIXME: Implement this. + return 0; +#endif +}; + +String DirAccess::get_filesystem_type() const { + return ""; //TODO this should be implemented +} + +bool DirAccess::is_hidden(const String &p_name) { + return p_name != "." && p_name != ".." && p_name.begins_with("."); } DirAccess::DirAccess() { - _skip_specials = true; - _read_file_result = 0; - _dir_open = false; - _dir = memnew(tinydir_dir); - _file = memnew(tinydir_file); -} -DirAccess::~DirAccess() { - if (is_dir_open()) { - close_dir(); + dir_stream = NULL; + _cisdir = false; + + next_is_dir = false; + _skip_specials = false; + + _cishidden = false; + _cisspecial = false; + + /* determine drive count */ + + // set current directory to an absolute path of the current directory + char real_current_dir_name[2048]; + ERR_FAIL_COND(getcwd(real_current_dir_name, 2048) == nullptr); + if (current_dir.parse_utf8(real_current_dir_name)) { + current_dir = real_current_dir_name; } - memdelete(_dir); - memdelete(_file); + change_dir(current_dir); +} + +DirAccess::~DirAccess() { + list_dir_end(); +} + +#endif + +/* +int DirAccess::get_current_drive() { + String path = get_current_dir().to_lower(); + for (int i = 0; i < get_drive_count(); i++) { + String d = get_drive(i).to_lower(); + if (path.begins_with(d)) { + return i; + } + } + + return 0; +} + +bool DirAccess::drives_are_shortcuts() { + return false; +} +*/ + +String DirAccess::get_current_dir_without_drive() { + return get_current_dir(); +} + +static Error _erase_recursive(DirAccess *da) { + List dirs; + List files; + + da->list_dir_begin(); + String n = da->get_next(); + while (n != String()) { + if (n != "." && n != "..") { + if (da->current_is_dir()) { + dirs.push_back(n); + } else { + files.push_back(n); + } + } + + n = da->get_next(); + } + + da->list_dir_end(); + + for (List::Element *E = dirs.front(); E; E = E->next()) { + Error err = da->change_dir(E->get()); + if (err == OK) { + err = _erase_recursive(da); + if (err) { + da->change_dir(".."); + return err; + } + err = da->change_dir(".."); + if (err) { + return err; + } + err = da->remove(da->get_current_dir().plus_file(E->get())); + if (err) { + return err; + } + } else { + return err; + } + } + + for (List::Element *E = files.front(); E; E = E->next()) { + Error err = da->remove(da->get_current_dir().plus_file(E->get())); + if (err) { + return err; + } + } + + return OK; +} + +Error DirAccess::erase_contents_recursive() { + return _erase_recursive(this); +} + +Error DirAccess::make_dir_recursive(String p_dir) { + if (p_dir.length() < 1) { + return OK; + }; + + String full_dir; + + if (p_dir.is_rel_path()) { + //append current + full_dir = get_current_dir().plus_file(p_dir); + + } else { + full_dir = p_dir; + } + + full_dir = full_dir.replace("\\", "/"); + + String base; + + if (full_dir.begins_with("res://")) { + base = "res://"; + } else if (full_dir.begins_with("user://")) { + base = "user://"; + } else if (full_dir.is_network_share_path()) { + int pos = full_dir.find("/", 2); + ERR_FAIL_COND_V(pos < 0, ERR_INVALID_PARAMETER); + pos = full_dir.find("/", pos + 1); + ERR_FAIL_COND_V(pos < 0, ERR_INVALID_PARAMETER); + base = full_dir.substr(0, pos + 1); + } else if (full_dir.begins_with("/")) { + base = "/"; + } else if (full_dir.find(":/") != -1) { + base = full_dir.substr(0, full_dir.find(":/") + 2); + } else { + ERR_FAIL_V(ERR_INVALID_PARAMETER); + } + + full_dir = full_dir.replace_first(base, "").simplify_path(); + + Vector subdirs = full_dir.split("/"); + + String curpath = base; + for (int i = 0; i < subdirs.size(); i++) { + curpath = curpath.plus_file(subdirs[i]); + Error err = make_dir(curpath); + if (err != OK && err != ERR_ALREADY_EXISTS) { + ERR_FAIL_V_MSG(err, "Could not create directory: " + curpath); + } + } + + return OK; +} + +DirAccess *DirAccess::create_for_path(const String &p_path) { + DirAccess *d = memnew(DirAccess()); + d->open(p_path); + return d; +} +DirAccess *DirAccess::create() { + return memnew(DirAccess()); +} + +Error DirAccess::open(const String &p_path) { + return change_dir(p_path); +} + +String DirAccess::get_full_path(const String &p_path) { + DirAccess d; + + d.change_dir(p_path); + String full = d.get_current_dir(); + + return full; +} + +Error DirAccess::copy(String p_from, String p_to, int p_chmod_flags) { + //printf("copy %s -> %s\n",p_from.ascii().get_data(),p_to.ascii().get_data()); + Error err; + FileAccess *fsrc = FileAccess::open(p_from, FileAccess::READ, &err); + + if (err) { + ERR_PRINT("Failed to open " + p_from); + return err; + } + + FileAccess *fdst = FileAccess::open(p_to, FileAccess::WRITE, &err); + if (err) { + fsrc->close(); + memdelete(fsrc); + ERR_PRINT("Failed to open " + p_to); + return err; + } + + const size_t copy_buffer_limit = 65536; // 64 KB + + fsrc->seek_end(0); + uint64_t size = fsrc->get_position(); + fsrc->seek(0); + err = OK; + size_t buffer_size = MIN(size * sizeof(uint8_t), copy_buffer_limit); + LocalVector buffer; + buffer.resize(buffer_size); + while (size > 0) { + if (fsrc->get_error() != OK) { + err = fsrc->get_error(); + break; + } + if (fdst->get_error() != OK) { + err = fdst->get_error(); + break; + } + + int bytes_read = fsrc->get_buffer(buffer.ptr(), buffer_size); + if (bytes_read <= 0) { + err = FAILED; + break; + } + fdst->store_buffer(buffer.ptr(), bytes_read); + + size -= bytes_read; + } + + if (err == OK && p_chmod_flags != -1) { + fdst->close(); + err = FileAccess::set_unix_permissions(p_to, p_chmod_flags); + // If running on a platform with no chmod support (i.e., Windows), don't fail + if (err == ERR_UNAVAILABLE) { + err = OK; + } + } + + memdelete(fsrc); + memdelete(fdst); + + return err; +} + +// Changes dir for the current scope, returning back to the original dir +// when scope exits +class DirChanger { + DirAccess *da; + String original_dir; + +public: + DirChanger(DirAccess *p_da, String p_dir) : + da(p_da), + original_dir(p_da->get_current_dir()) { + p_da->change_dir(p_dir); + } + + ~DirChanger() { + da->change_dir(original_dir); + } +}; + +Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flags, bool p_copy_links) { + List dirs; + + String curdir = get_current_dir(); + list_dir_begin(); + String n = get_next(); + while (n != String()) { + if (n != "." && n != "..") { + if (p_copy_links && is_link(get_current_dir().plus_file(n))) { + create_link(read_link(get_current_dir().plus_file(n)), p_to + n); + } else if (current_is_dir()) { + dirs.push_back(n); + } else { + const String &rel_path = n; + if (!n.is_rel_path()) { + list_dir_end(); + return ERR_BUG; + } + Error err = copy(get_current_dir().plus_file(n), p_to + rel_path, p_chmod_flags); + if (err) { + list_dir_end(); + return err; + } + } + } + + n = get_next(); + } + + list_dir_end(); + + for (List::Element *E = dirs.front(); E; E = E->next()) { + String rel_path = E->get(); + String target_dir = p_to + rel_path; + if (!p_target_da->dir_exists(target_dir)) { + Error err = p_target_da->make_dir(target_dir); + ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + target_dir + "'."); + } + + Error err = change_dir(E->get()); + ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot change current directory to '" + E->get() + "'."); + + err = _copy_dir(p_target_da, p_to + rel_path + "/", p_chmod_flags, p_copy_links); + if (err) { + change_dir(".."); + ERR_FAIL_V_MSG(err, "Failed to copy recursively."); + } + err = change_dir(".."); + ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to go back."); + } + + return OK; +} + +Error DirAccess::copy_dir(String p_from, String p_to, int p_chmod_flags, bool p_copy_links) { + ERR_FAIL_COND_V_MSG(!dir_exists(p_from), ERR_FILE_NOT_FOUND, "Source directory doesn't exist."); + + DirAccess *target_da = DirAccess::create_for_path(p_to); + ERR_FAIL_COND_V_MSG(!target_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_to + "'."); + + if (!target_da->dir_exists(p_to)) { + Error err = target_da->make_dir_recursive(p_to); + if (err) { + memdelete(target_da); + } + ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + p_to + "'."); + } + + if (!p_to.ends_with("/")) { + p_to = p_to + "/"; + } + + DirChanger dir_changer(this, p_from); + Error err = _copy_dir(target_da, p_to, p_chmod_flags, p_copy_links); + memdelete(target_da); + + return err; +} + +bool DirAccess::exists(String p_dir) { + DirAccess *da = DirAccess::create_for_path(p_dir); + bool valid = da->change_dir(p_dir) == OK; + memdelete(da); + return valid; +} + +String DirAccess::get_filesystem_abspath_for(String p_path) { + DirAccess d; + + d.change_dir(p_path); + String full = d.get_current_dir(); + + return full; +} + +bool DirAccess::is_special(const String &p_path) { + if (p_path.size() > 2) { + return false; + } + + return p_path == "." || p_path == ".."; } diff --git a/sfw/core/dir_access.h b/sfw/core/dir_access.h index f747c0c..582c400 100644 --- a/sfw/core/dir_access.h +++ b/sfw/core/dir_access.h @@ -2,48 +2,122 @@ #ifndef DIR_ACCESS_H #define DIR_ACCESS_H +/*************************************************************************/ +/* dir_access.h */ +/* From https://github.com/Relintai/pandemonium_engine (MIT) */ +/*************************************************************************/ + //--STRIP #include "core/error_list.h" #include "core/ustring.h" //--STRIP -struct tinydir_file; -struct tinydir_dir; +#if defined(_WIN64) || defined(_WIN32) +#else +struct __dirstream; +typedef struct __dirstream DIR; +#endif class DirAccess { + Error _copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flags, bool p_copy_links); + public: - Error open_dir(const String &path, bool skip_specials = true); - Error open_dir(const char *path, bool skip_specials = true); - void close_dir(); + virtual Error list_dir_begin(bool skip_specials = false); ///< This starts dir listing + virtual String get_next(); + virtual bool current_is_dir() const; + virtual bool current_is_hidden() const; + virtual bool current_is_file() const; + virtual bool current_is_special_dir() const; - bool has_next(); - bool read(); - bool next(); + virtual void list_dir_end(); ///< - bool current_is_ok(); - String current_get_name(); - String current_get_path(); - String current_get_extension(); - const char *current_get_name_cstr(); - const char *current_get_path_cstr(); - const char *current_get_extension_cstr(); - bool current_is_file(); - bool current_is_dir(); - bool current_is_special_dir(); + virtual int get_drive_count(); + virtual String get_drive(int p_drive); + virtual int get_current_drive(); + virtual bool drives_are_shortcuts(); - bool is_dir_open(); - bool is_dir_closed(); + virtual Error change_dir(String p_dir); ///< can be relative or absolute, return false on success + virtual String get_current_dir(); ///< return current dir location + virtual String get_current_dir_without_drive(); + virtual Error make_dir(String p_dir); + virtual Error make_dir_recursive(String p_dir); + virtual Error erase_contents_recursive(); //super dangerous, use with care! + + virtual bool file_exists(String p_file); + virtual bool dir_exists(String p_dir); + static bool exists(String p_dir); + virtual uint64_t get_space_left(); + + Error copy_dir(String p_from, String p_to, int p_chmod_flags = -1, bool p_copy_links = false); + virtual Error copy(String p_from, String p_to, int p_chmod_flags = -1); + virtual Error rename(String p_from, String p_to); + virtual Error remove(String p_name); + + virtual bool is_link(String p_file); + virtual String read_link(String p_file); + virtual Error create_link(String p_source, String p_target); + + virtual uint64_t get_modified_time(String p_file); + + virtual String get_filesystem_type() const; + static String get_full_path(const String &p_path); + + static DirAccess *create_for_path(const String &p_path); + static DirAccess *create(); + + Error open(const String &p_path); + + static String get_filesystem_abspath_for(String p_path); + + static bool is_special(const String &p_path); DirAccess(); virtual ~DirAccess(); -private: - bool _skip_specials; - int _read_file_result; - tinydir_dir *_dir; - tinydir_file *_file; +protected: +#if defined(_WIN64) || defined(_WIN32) +#else + String current_dir; + virtual String fix_unicode_name(const char *p_name) const { return String::utf8(p_name); } + virtual bool is_hidden(const String &p_name); +#endif - bool _dir_open; + bool next_is_dir; + bool _skip_specials; + +#if defined(_WIN64) || defined(_WIN32) +#else + DIR *dir_stream; + + bool _cisdir; + bool _cishidden; + bool _cisspecial; +#endif +}; + +struct DirAccessRef { + DirAccess *f; + + _FORCE_INLINE_ bool is_null() const { return f == nullptr; } + _FORCE_INLINE_ bool is_valid() const { return f != nullptr; } + + _FORCE_INLINE_ operator bool() const { return f != nullptr; } + _FORCE_INLINE_ operator DirAccess *() { return f; } + + _FORCE_INLINE_ DirAccess *operator->() { + return f; + } + + DirAccessRef(DirAccess *fa) { f = fa; } + DirAccessRef(DirAccessRef &&other) { + f = other.f; + other.f = nullptr; + } + ~DirAccessRef() { + if (f) { + memdelete(f); + } + } }; #endif diff --git a/sfw/core/file_access.cpp b/sfw/core/file_access.cpp index 454846b..6f312c6 100644 --- a/sfw/core/file_access.cpp +++ b/sfw/core/file_access.cpp @@ -1,98 +1,843 @@ +/*************************************************************************/ +/* file_access.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* PANDEMONIUM ENGINE */ +/* https://github.com/Relintai/pandemonium_engine */ +/*************************************************************************/ +/* Copyright (c) 2022-present Péter Magyar. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + //--STRIP #include "file_access.h" +#include "core/marshalls.h" + #include //--STRIP +#if defined(_WIN64) || defined(_WIN32) +#else +#include +#include +#include -String FileAccess::read_file(const String &path) { - FILE *f = fopen(path.utf8().get_data(), "r"); +#include - ERR_FAIL_COND_V_MSG(!f, String(), "Error opening file! " + path); +#include - fseek(f, 0, SEEK_END); - long fsize = ftell(f); - fseek(f, 0, SEEK_SET); /* same as rewind(f); */ +#ifdef MSVC +#define S_ISREG(m) ((m)&_S_IFREG) +#include +#endif +#ifndef S_ISREG +#define S_ISREG(m) ((m)&S_IFREG) +#endif - CharString cs; - cs.resize(fsize + 1); // +1 for the null terminator +#ifndef NO_FCNTL +#include +#else +#include +#endif - fread(cs.ptrw(), 1, fsize, f); - fclose(f); +#endif - return String::utf8(cs.ptr()); +#if defined(_WIN64) || defined(_WIN32) +#else + +void FileAccess::check_errors() const { + ERR_FAIL_COND_MSG(!f, "File must be opened before use."); + + if (feof(f)) { + last_error = ERR_FILE_EOF; + } } -Vector FileAccess::read_file_bin(const String &path) { - FILE *f = fopen(path.utf8().get_data(), "rb"); +Error FileAccess::_open(const String &p_path, int p_mode_flags) { + if (f) { + fclose(f); + } + f = nullptr; - Vector fd; + path_src = p_path; + path = fix_path(p_path); + //printf("opening %s, %i\n", path.utf8().get_data(), Memory::get_static_mem_usage()); - ERR_FAIL_COND_V_MSG(!f, fd, "Error opening file! " + path); + ERR_FAIL_COND_V_MSG(f, ERR_ALREADY_IN_USE, "File is already in use."); + const char *mode_string; - fseek(f, 0, SEEK_END); - long fsize = ftell(f); - fseek(f, 0, SEEK_SET); /* same as rewind(f); */ - - fd.resize(fsize); - - fread(fd.ptrw(), 1, fsize, f); - fclose(f); - - return fd; -} - -Error FileAccess::read_file_into_bin(const String &path, Vector *data) { - if (!data) { - return ERR_PARAMETER_RANGE_ERROR; + if (p_mode_flags == READ) { + mode_string = "rb"; + } else if (p_mode_flags == WRITE) { + mode_string = "wb"; + } else if (p_mode_flags == READ_WRITE) { + mode_string = "rb+"; + } else if (p_mode_flags == WRITE_READ) { + mode_string = "wb+"; + } else { + return ERR_INVALID_PARAMETER; } - FILE *f = fopen(path.utf8().get_data(), "rb"); + /* pretty much every implementation that uses fopen as primary + backend (unix-compatible mostly) supports utf8 encoding */ - if (!f) { - return ERR_FILE_CANT_OPEN; + //printf("opening %s as %s\n", p_path.utf8().get_data(), path.utf8().get_data()); + struct stat st; + int err = stat(path.utf8().get_data(), &st); + if (!err) { + switch (st.st_mode & S_IFMT) { + case S_IFLNK: + case S_IFREG: + break; + default: + return ERR_FILE_CANT_OPEN; + } } - fseek(f, 0, SEEK_END); - long fsize = ftell(f); - fseek(f, 0, SEEK_SET); /* same as rewind(f); */ + if (is_backup_save_enabled() && (p_mode_flags & WRITE) && !(p_mode_flags & READ)) { + save_path = path; + path = path + ".tmp"; + } - data->resize(fsize); + f = fopen(path.utf8().get_data(), mode_string); - fread(data->ptrw(), 1, fsize, f); - fclose(f); + if (f == nullptr) { + switch (errno) { + case ENOENT: { + last_error = ERR_FILE_NOT_FOUND; + } break; + default: { + last_error = ERR_FILE_CANT_OPEN; + } break; + } + return last_error; + } + // Set close on exec to avoid leaking it to subprocesses. + int fd = fileno(f); + + if (fd != -1) { +#if defined(NO_FCNTL) + unsigned long par = 0; + ioctl(fd, FIOCLEX, &par); +#else + int opts = fcntl(fd, F_GETFD); + fcntl(fd, F_SETFD, opts | FD_CLOEXEC); +#endif + } + + last_error = OK; + flags = p_mode_flags; return OK; } -Error FileAccess::write_file(const String &path, const String &str) { - FILE *f = fopen(path.utf8().get_data(), "w"); - +void FileAccess::close() { if (!f) { - return ERR_FILE_CANT_OPEN; + return; } - fwrite(str.utf8().ptr(), sizeof(char), str.size(), f); fclose(f); + f = nullptr; - return OK; -} - -Error FileAccess::write_file_bin(const String &path, const Vector &data) { - FILE *f = fopen(path.utf8().get_data(), "wb"); - - if (!f) { - return ERR_FILE_CANT_OPEN; + if (close_notification_func) { + close_notification_func(path, flags); } - fwrite(data.ptr(), sizeof(uint8_t), data.size(), f); - fclose(f); + if (save_path != "") { + int rename_error = rename((save_path + ".tmp").utf8().get_data(), save_path.utf8().get_data()); - return OK; + if (rename_error && close_fail_notify) { + close_fail_notify(save_path); + } + + save_path = ""; + ERR_FAIL_COND(rename_error != 0); + } } -FileAccess::FileAccess() { +bool FileAccess::is_open() const { + return (f != nullptr); } + +String FileAccess::get_path() const { + return path_src; +} + +String FileAccess::get_path_absolute() const { + return path; +} + +void FileAccess::seek(uint64_t p_position) { + ERR_FAIL_COND_MSG(!f, "File must be opened before use."); + + last_error = OK; + if (fseeko(f, p_position, SEEK_SET)) { + check_errors(); + } +} + +void FileAccess::seek_end(int64_t p_position) { + ERR_FAIL_COND_MSG(!f, "File must be opened before use."); + + if (fseeko(f, p_position, SEEK_END)) { + check_errors(); + } +} + +uint64_t FileAccess::get_position() const { + ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); + + int64_t pos = ftello(f); + if (pos < 0) { + check_errors(); + ERR_FAIL_V(0); + } + return pos; +} + +uint64_t FileAccess::get_len() const { + ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); + + int64_t pos = ftello(f); + ERR_FAIL_COND_V(pos < 0, 0); + ERR_FAIL_COND_V(fseeko(f, 0, SEEK_END), 0); + int64_t size = ftello(f); + ERR_FAIL_COND_V(size < 0, 0); + ERR_FAIL_COND_V(fseeko(f, pos, SEEK_SET), 0); + + return size; +} + +bool FileAccess::eof_reached() const { + return last_error == ERR_FILE_EOF; +} + +uint8_t FileAccess::get_8() const { + ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); + uint8_t b; + if (fread(&b, 1, 1, f) == 0) { + check_errors(); + b = '\0'; + } + return b; +} + +uint64_t FileAccess::get_buffer(uint8_t *p_dst, uint64_t p_length) const { + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); + ERR_FAIL_COND_V_MSG(!f, -1, "File must be opened before use."); + + uint64_t read = fread(p_dst, 1, p_length, f); + check_errors(); + return read; +}; + +Error FileAccess::get_error() const { + return last_error; +} + +void FileAccess::flush() { + ERR_FAIL_COND_MSG(!f, "File must be opened before use."); + fflush(f); +} + +void FileAccess::store_8(uint8_t p_dest) { + ERR_FAIL_COND_MSG(!f, "File must be opened before use."); + ERR_FAIL_COND(fwrite(&p_dest, 1, 1, f) != 1); +} + +void FileAccess::store_buffer(const uint8_t *p_src, uint64_t p_length) { + ERR_FAIL_COND_MSG(!f, "File must be opened before use."); + ERR_FAIL_COND(!p_src && p_length > 0); + ERR_FAIL_COND(fwrite(p_src, 1, p_length, f) != p_length); +} + +bool FileAccess::file_exists(const String &p_path) { + int err; + struct stat st; + String filename = fix_path(p_path); + + // Does the name exist at all? + err = stat(filename.utf8().get_data(), &st); + if (err) { + return false; + } + +#if defined(_WIN64) || defined(_WIN32) + if (_access(filename.utf8().get_data(), 4) == -1) { + return false; + } +#else + // See if we have access to the file + if (access(filename.utf8().get_data(), F_OK)) { + return false; + } +#endif + + // See if this is a regular file + switch (st.st_mode & S_IFMT) { + case S_IFLNK: + case S_IFREG: + return true; + default: + return false; + } +} + +uint64_t FileAccess::_get_modified_time(const String &p_file) { + String file = fix_path(p_file); + struct stat flags; + int err = stat(file.utf8().get_data(), &flags); + + if (!err) { + return flags.st_mtime; + } else { + LOG_TRACE("Failed to get modified time for: " + p_file + ""); + return 0; + }; +} + +uint32_t FileAccess::_get_unix_permissions(const String &p_file) { + String file = fix_path(p_file); + struct stat flags; + int err = stat(file.utf8().get_data(), &flags); + + if (!err) { + return flags.st_mode & 0x7FF; //only permissions + } else { + ERR_FAIL_V_MSG(0, "Failed to get unix permissions for: " + p_file + "."); + }; +} + +Error FileAccess::_set_unix_permissions(const String &p_file, uint32_t p_permissions) { + String file = fix_path(p_file); + + int err = chmod(file.utf8().get_data(), p_permissions); + if (!err) { + return OK; + } + + return FAILED; +} + +FileCloseNotificationFunc FileAccess::close_notification_func = nullptr; + +FileAccess::FileAccess() : + f(nullptr), + flags(0), + last_error(OK) { + endian_swap = false; + real_is_double = false; +} + FileAccess::~FileAccess() { + close(); } + +#endif + +FileAccess::FileCloseFailNotify FileAccess::close_fail_notify = nullptr; + +bool FileAccess::backup_save = false; + +FileAccess *FileAccess::create() { + return memnew(FileAccess()); +} + +bool FileAccess::exists(const String &p_name) { + FileAccess *f = open(p_name, READ); + if (!f) { + return false; + } + memdelete(f); + return true; +} + +Error FileAccess::reopen(const String &p_path, int p_mode_flags) { + return _open(p_path, p_mode_flags); +}; + +FileAccess *FileAccess::open(const String &p_path, int p_mode_flags, Error *r_error) { + //try packed data first + + FileAccess *ret = nullptr; + + ret = create(); + Error err = ret->_open(p_path, p_mode_flags); + + if (r_error) { + *r_error = err; + } + if (err != OK) { + memdelete(ret); + ret = nullptr; + } + + return ret; +} + +String FileAccess::fix_path(const String &p_path) const { + //helper used by file accesses that use a single filesystem + + String r_path = p_path.replace("\\", "/"); + + return r_path; +} + +/* these are all implemented for ease of porting, then can later be optimized */ + +uint16_t FileAccess::get_16() const { + uint16_t res; + uint8_t a, b; + + a = get_8(); + b = get_8(); + + if (endian_swap) { + SWAP(a, b); + } + + res = b; + res <<= 8; + res |= a; + + return res; +} +uint32_t FileAccess::get_32() const { + uint32_t res; + uint16_t a, b; + + a = get_16(); + b = get_16(); + + if (endian_swap) { + SWAP(a, b); + } + + res = b; + res <<= 16; + res |= a; + + return res; +} +uint64_t FileAccess::get_64() const { + uint64_t res; + uint32_t a, b; + + a = get_32(); + b = get_32(); + + if (endian_swap) { + SWAP(a, b); + } + + res = b; + res <<= 32; + res |= a; + + return res; +} + +float FileAccess::get_float() const { + MarshallFloat m; + m.i = get_32(); + return m.f; +}; + +real_t FileAccess::get_real() const { + if (real_is_double) { + return get_double(); + } else { + return get_float(); + } +} + +double FileAccess::get_double() const { + MarshallDouble m; + m.l = get_64(); + return m.d; +}; + +String FileAccess::get_token() const { + CharString token; + + CharType c = get_8(); + + while (!eof_reached()) { + if (c <= ' ') { + if (token.length()) { + break; + } + } else { + token += c; + } + c = get_8(); + } + + return String::utf8(token.get_data()); +} + +class CharBuffer { + Vector vector; + char stack_buffer[256]; + + char *buffer; + int capacity; + int written; + + bool grow() { + if (vector.resize(next_power_of_2(1 + written)) != OK) { + return false; + } + + if (buffer == stack_buffer) { // first chunk? + + for (int i = 0; i < written; i++) { + vector.write[i] = stack_buffer[i]; + } + } + + buffer = vector.ptrw(); + capacity = vector.size(); + ERR_FAIL_COND_V(written >= capacity, false); + + return true; + } + +public: + _FORCE_INLINE_ CharBuffer() : + buffer(stack_buffer), + capacity(sizeof(stack_buffer) / sizeof(char)), + written(0) { + } + + _FORCE_INLINE_ void push_back(char c) { + if (written >= capacity) { + ERR_FAIL_COND(!grow()); + } + + buffer[written++] = c; + } + + _FORCE_INLINE_ const char *get_data() const { + return buffer; + } +}; + +String FileAccess::get_line() const { + CharBuffer line; + + CharType c = get_8(); + + while (!eof_reached()) { + if (c == '\n' || c == '\0') { + line.push_back(0); + return String::utf8(line.get_data()); + } else if (c != '\r') { + line.push_back(c); + } + + c = get_8(); + } + line.push_back(0); + return String::utf8(line.get_data()); +} + +Vector FileAccess::get_csv_line(const String &p_delim) const { + ERR_FAIL_COND_V_MSG(p_delim.length() != 1, Vector(), "Only single character delimiters are supported to parse CSV lines."); + ERR_FAIL_COND_V_MSG(p_delim[0] == '"', Vector(), "The double quotation mark character (\") is not supported as a delimiter for CSV lines."); + + String line; + + // CSV can support entries with line breaks as long as they are enclosed + // in double quotes. So our "line" might be more than a single line in the + // text file. + int qc = 0; + do { + if (eof_reached()) { + break; + } + line += get_line() + "\n"; + qc = 0; + for (int i = 0; i < line.length(); i++) { + if (line[i] == '"') { + qc++; + } + } + } while (qc % 2); + + // Remove the extraneous newline we've added above. + line = line.substr(0, line.length() - 1); + + Vector strings; + + bool in_quote = false; + String current; + for (int i = 0; i < line.length(); i++) { + CharType c = line[i]; + // A delimiter ends the current entry, unless it's in a quoted string. + if (!in_quote && c == p_delim[0]) { + strings.push_back(current); + current = String(); + } else if (c == '"') { + // Doubled quotes are escapes for intentional quotes in the string. + if (line[i + 1] == '"' && in_quote) { + current += '"'; + i++; + } else { + in_quote = !in_quote; + } + } else { + current += c; + } + } + strings.push_back(current); + + return strings; +} + +/* +uint64_t FileAccess::get_buffer(uint8_t *p_dst, uint64_t p_length) const { + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); + + uint64_t i = 0; + for (i = 0; i < p_length && !eof_reached(); i++) { + p_dst[i] = get_8(); + } + + return i; +} +*/ + +String FileAccess::get_as_utf8_string(bool p_skip_cr) const { + Vector sourcef; + uint64_t len = get_len(); + sourcef.resize(len + 1); + + uint8_t *w = sourcef.ptrw(); + uint64_t r = get_buffer(w, len); + ERR_FAIL_COND_V(r != len, String()); + w[len] = 0; + + String s; + if (s.parse_utf8((const char *)w, -1, p_skip_cr)) { + return String(); + } + + return s; +} + +void FileAccess::store_16(uint16_t p_dest) { + uint8_t a, b; + + a = p_dest & 0xFF; + b = p_dest >> 8; + + if (endian_swap) { + SWAP(a, b); + } + + store_8(a); + store_8(b); +} +void FileAccess::store_32(uint32_t p_dest) { + uint16_t a, b; + + a = p_dest & 0xFFFF; + b = p_dest >> 16; + + if (endian_swap) { + SWAP(a, b); + } + + store_16(a); + store_16(b); +} +void FileAccess::store_64(uint64_t p_dest) { + uint32_t a, b; + + a = p_dest & 0xFFFFFFFF; + b = p_dest >> 32; + + if (endian_swap) { + SWAP(a, b); + } + + store_32(a); + store_32(b); +} + +void FileAccess::store_real(real_t p_real) { + if (sizeof(real_t) == 4) { + store_float(p_real); + } else { + store_double(p_real); + } +} + +void FileAccess::store_float(float p_dest) { + MarshallFloat m; + m.f = p_dest; + store_32(m.i); +}; + +void FileAccess::store_double(double p_dest) { + MarshallDouble m; + m.d = p_dest; + store_64(m.l); +}; + +uint64_t FileAccess::get_modified_time(const String &p_file) { + FileAccess *fa = create(); + ERR_FAIL_COND_V_MSG(!fa, 0, "Cannot create FileAccess for path '" + p_file + "'."); + + uint64_t mt = fa->_get_modified_time(p_file); + memdelete(fa); + return mt; +} + +uint32_t FileAccess::get_unix_permissions(const String &p_file) { + FileAccess *fa = create(); + ERR_FAIL_COND_V_MSG(!fa, 0, "Cannot create FileAccess for path '" + p_file + "'."); + + uint32_t mt = fa->_get_unix_permissions(p_file); + memdelete(fa); + return mt; +} + +Error FileAccess::set_unix_permissions(const String &p_file, uint32_t p_permissions) { + FileAccess *fa = create(); + ERR_FAIL_COND_V_MSG(!fa, ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'."); + + Error err = fa->_set_unix_permissions(p_file, p_permissions); + memdelete(fa); + return err; +} + +void FileAccess::store_string(const String &p_string) { + if (p_string.length() == 0) { + return; + } + + CharString cs = p_string.utf8(); + store_buffer((uint8_t *)&cs[0], cs.length()); +} + +void FileAccess::store_pascal_string(const String &p_string) { + CharString cs = p_string.utf8(); + store_32(cs.length()); + store_buffer((uint8_t *)&cs[0], cs.length()); +}; + +String FileAccess::get_pascal_string() { + uint32_t sl = get_32(); + CharString cs; + cs.resize(sl + 1); + get_buffer((uint8_t *)cs.ptr(), sl); + cs[sl] = 0; + + String ret; + ret.parse_utf8(cs.ptr()); + + return ret; +}; + +void FileAccess::store_line(const String &p_line) { + store_string(p_line); + store_8('\n'); +} + +void FileAccess::store_csv_line(const Vector &p_values, const String &p_delim) { + ERR_FAIL_COND(p_delim.length() != 1); + + String line = ""; + int size = p_values.size(); + for (int i = 0; i < size; ++i) { + String value = p_values[i]; + + if (value.find("\"") != -1 || value.find(p_delim) != -1 || value.find("\n") != -1) { + value = "\"" + value.replace("\"", "\"\"") + "\""; + } + if (i < size - 1) { + value += p_delim; + } + + line += value; + } + + store_line(line); +} + +void FileAccess::store_buffer_vec(const Vector &data) { + store_buffer(data.ptr(), data.size()); +} + +/* +void FileAccess::store_buffer(const uint8_t *p_src, uint64_t p_length) { + ERR_FAIL_COND(!p_src && p_length > 0); + for (uint64_t i = 0; i < p_length; i++) { + store_8(p_src[i]); + } +} +*/ + +Vector FileAccess::get_file_as_array(const String &p_path, Error *r_error) { + FileAccess *f = FileAccess::open(p_path, READ, r_error); + if (!f) { + if (r_error) { // if error requested, do not throw error + return Vector(); + } + ERR_FAIL_V_MSG(Vector(), "Can't open file from path '" + String(p_path) + "'."); + } + Vector data; + data.resize(f->get_len()); + f->get_buffer(data.ptrw(), data.size()); + memdelete(f); + return data; +} + +String FileAccess::get_file_as_string(const String &p_path, Error *r_error) { + Error err; + Vector array = get_file_as_array(p_path, &err); + if (r_error) { + *r_error = err; + } + if (err != OK) { + if (r_error) { + return String(); + } + ERR_FAIL_V_MSG(String(), "Can't get file as string from path '" + String(p_path) + "'."); + } + + String ret; + ret.parse_utf8((const char *)array.ptr(), array.size()); + return ret; +} + +/* +FileAccess::FileAccess() { + endian_swap = false; + real_is_double = false; +}; +*/ diff --git a/sfw/core/file_access.h b/sfw/core/file_access.h index 83d0377..9ec475d 100644 --- a/sfw/core/file_access.h +++ b/sfw/core/file_access.h @@ -2,27 +2,202 @@ #ifndef FILE_ACCESS_H #define FILE_ACCESS_H +/*************************************************************************/ +/* file_access.h */ +/*************************************************************************/ +/* This file is part of: */ +/* PANDEMONIUM ENGINE */ +/* https://github.com/Relintai/pandemonium_engine */ +/*************************************************************************/ +/* Copyright (c) 2022-present Péter Magyar. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + //--STRIP #include "core/error_list.h" +#include "core/math_defs.h" #include "core/ustring.h" //--STRIP +#if defined(_WIN64) || defined(_WIN32) +#else +struct _IO_FILE; +typedef struct _IO_FILE FILE; + +typedef void (*FileCloseNotificationFunc)(const String &p_file, int p_flags); +#endif + class FileAccess { public: - //TODO should probably have some simple buffered open / close / write / read api. + typedef void (*FileCloseFailNotify)(const String &); - String read_file(const String &path); + bool endian_swap; + bool real_is_double; - Vector read_file_bin(const String &path); - Error read_file_into_bin(const String &path, Vector *data); + virtual uint32_t _get_unix_permissions(const String &p_file); + virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions); - Error write_file(const String &path, const String &str); - Error write_file_bin(const String &path, const Vector &data); +protected: + String fix_path(const String &p_path) const; + virtual Error _open(const String &p_path, int p_mode_flags); ///< open a file + virtual uint64_t _get_modified_time(const String &p_file); + + static FileCloseFailNotify close_fail_notify; + +private: + static bool backup_save; + +public: + static void set_file_close_fail_notify_callback(FileCloseFailNotify p_cbk) { close_fail_notify = p_cbk; } + + enum ModeFlags { + READ = 1, + WRITE = 2, + READ_WRITE = 3, + WRITE_READ = 7, + }; + + virtual void close(); ///< close a file + virtual bool is_open() const; ///< true when file is open + + virtual String get_path() const; /// returns the path for the current open file + virtual String get_path_absolute() const; /// returns the absolute path for the current open file + + virtual void seek(uint64_t p_position); ///< seek to a given position + virtual void seek_end(int64_t p_position); ///< seek from the end of file with negative offset + virtual uint64_t get_position() const; ///< get position in the file + virtual uint64_t get_len() const; ///< get size of the file + + virtual bool eof_reached() const; ///< reading passed EOF + + virtual uint8_t get_8() const; ///< get a byte + virtual uint16_t get_16() const; ///< get 16 bits uint + virtual uint32_t get_32() const; ///< get 32 bits uint + virtual uint64_t get_64() const; ///< get 64 bits uint + + virtual float get_float() const; + virtual double get_double() const; + virtual real_t get_real() const; + + virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; ///< get an array of bytes + virtual String get_line() const; + virtual String get_token() const; + virtual Vector get_csv_line(const String &p_delim = ",") const; + virtual String get_as_utf8_string(bool p_skip_cr = true) const; // Skip CR by default for compat. + + /**< use this for files WRITTEN in _big_ endian machines (ie, amiga/mac) + * It's not about the current CPU type but file formats. + * this flags get reset to false (little endian) on each open + */ + + virtual void set_endian_swap(bool p_swap) { endian_swap = p_swap; } + inline bool get_endian_swap() const { return endian_swap; } + + virtual Error get_error() const; ///< get last error + + virtual void flush(); + virtual void store_8(uint8_t p_dest); ///< store a byte + virtual void store_16(uint16_t p_dest); ///< store 16 bits uint + virtual void store_32(uint32_t p_dest); ///< store 32 bits uint + virtual void store_64(uint64_t p_dest); ///< store 64 bits uint + + virtual void store_float(float p_dest); + virtual void store_double(double p_dest); + virtual void store_real(real_t p_real); + + virtual void store_string(const String &p_string); + virtual void store_line(const String &p_line); + virtual void store_csv_line(const Vector &p_values, const String &p_delim = ","); + + virtual void store_pascal_string(const String &p_string); + virtual String get_pascal_string(); + + void store_buffer_vec(const Vector &data); ///< store an array of bytes + virtual void store_buffer(const uint8_t *p_src, uint64_t p_length); ///< store an array of bytes + + virtual bool file_exists(const String &p_name); ///< return true if a file exists + + virtual Error reopen(const String &p_path, int p_mode_flags); ///< does not change the AccessType + + static FileAccess *create(); /// Create a file access (for the current platform) this is the only portable way of accessing files. + static FileAccess *open(const String &p_path, int p_mode_flags, Error *r_error = nullptr); /// Create a file access (for the current platform) this is the only portable way of accessing files. + static bool exists(const String &p_name); ///< return true if a file exists + static uint64_t get_modified_time(const String &p_file); + static uint32_t get_unix_permissions(const String &p_file); + static Error set_unix_permissions(const String &p_file, uint32_t p_permissions); + + static void set_backup_save(bool p_enable) { backup_save = p_enable; }; + static bool is_backup_save_enabled() { return backup_save; }; + + static Vector get_file_as_array(const String &p_path, Error *r_error = nullptr); + static String get_file_as_string(const String &p_path, Error *r_error = nullptr); FileAccess(); virtual ~FileAccess(); -private: +#if defined(_WIN64) || defined(_WIN32) +#else + static FileCloseNotificationFunc close_notification_func; +#endif + +protected: +#if defined(_WIN64) || defined(_WIN32) +#else + void check_errors() const; + + FILE *f; + int flags; + + mutable Error last_error; + String save_path; + String path; + String path_src; +#endif +}; + +struct FileAccessRef { + FileAccess *f; + + _FORCE_INLINE_ bool is_null() const { return f == nullptr; } + _FORCE_INLINE_ bool is_valid() const { return f != nullptr; } + + _FORCE_INLINE_ operator bool() const { return f != nullptr; } + _FORCE_INLINE_ operator FileAccess *() { return f; } + + _FORCE_INLINE_ FileAccess *operator->() { + return f; + } + + FileAccessRef(FileAccess *fa) { f = fa; } + FileAccessRef(FileAccessRef &&other) { + f = other.f; + other.f = nullptr; + } + ~FileAccessRef() { + if (f) { + memdelete(f); + } + } }; #endif diff --git a/sfw/core/marshalls.h b/sfw/core/marshalls.h new file mode 100644 index 0000000..cf3bed3 --- /dev/null +++ b/sfw/core/marshalls.h @@ -0,0 +1,142 @@ +#ifndef MARSHALLS_H +#define MARSHALLS_H + +/*************************************************************************/ +/* marshalls.h */ +/* From https://github.com/Relintai/pandemonium_engine (MIT) */ +/*************************************************************************/ + +#include "core/int_types.h" +#include "core/math_defs.h" + +/** + * Miscellaneous helpers for marshalling data types, and encoding + * in an endian independent way + */ + +union MarshallFloat { + uint32_t i; ///< int + float f; ///< float +}; + +union MarshallDouble { + uint64_t l; ///< long long + double d; ///< double +}; + +static inline unsigned int encode_uint16(uint16_t p_uint, uint8_t *p_arr) { + for (int i = 0; i < 2; i++) { + *p_arr = p_uint & 0xFF; + p_arr++; + p_uint >>= 8; + } + + return sizeof(uint16_t); +} + +static inline unsigned int encode_uint32(uint32_t p_uint, uint8_t *p_arr) { + for (int i = 0; i < 4; i++) { + *p_arr = p_uint & 0xFF; + p_arr++; + p_uint >>= 8; + } + + return sizeof(uint32_t); +} + +static inline unsigned int encode_float(float p_float, uint8_t *p_arr) { + MarshallFloat mf; + mf.f = p_float; + encode_uint32(mf.i, p_arr); + + return sizeof(uint32_t); +} + +static inline unsigned int encode_uint64(uint64_t p_uint, uint8_t *p_arr) { + for (int i = 0; i < 8; i++) { + *p_arr = p_uint & 0xFF; + p_arr++; + p_uint >>= 8; + } + + return sizeof(uint64_t); +} + +static inline unsigned int encode_double(double p_double, uint8_t *p_arr) { + MarshallDouble md; + md.d = p_double; + encode_uint64(md.l, p_arr); + + return sizeof(uint64_t); +} + +static inline int encode_cstring(const char *p_string, uint8_t *p_data) { + int len = 0; + + while (*p_string) { + if (p_data) { + *p_data = (uint8_t)*p_string; + p_data++; + } + p_string++; + len++; + }; + + if (p_data) { + *p_data = 0; + } + return len + 1; +} + +static inline uint16_t decode_uint16(const uint8_t *p_arr) { + uint16_t u = 0; + + for (int i = 0; i < 2; i++) { + uint16_t b = *p_arr; + b <<= (i * 8); + u |= b; + p_arr++; + } + + return u; +} + +static inline uint32_t decode_uint32(const uint8_t *p_arr) { + uint32_t u = 0; + + for (int i = 0; i < 4; i++) { + uint32_t b = *p_arr; + b <<= (i * 8); + u |= b; + p_arr++; + } + + return u; +} + +static inline float decode_float(const uint8_t *p_arr) { + MarshallFloat mf; + mf.i = decode_uint32(p_arr); + return mf.f; +} + +static inline uint64_t decode_uint64(const uint8_t *p_arr) { + uint64_t u = 0; + + for (int i = 0; i < 8; i++) { + uint64_t b = (*p_arr) & 0xFF; + b <<= (i * 8); + u |= b; + p_arr++; + } + + return u; +} + +static inline double decode_double(const uint8_t *p_arr) { + MarshallDouble md; + md.l = decode_uint64(p_arr); + return md.d; +} + +#endif diff --git a/tools/merger/sfw_core.cpp.inl b/tools/merger/sfw_core.cpp.inl index 1d51836..df7099e 100644 --- a/tools/merger/sfw_core.cpp.inl +++ b/tools/merger/sfw_core.cpp.inl @@ -188,8 +188,6 @@ //--STRIP {{FILE:sfw/core/file_access.cpp}} -{{FILE:sfw/core/3rd_tinydir.h}} - //--STRIP //#include "dir_access.h" //#include "3rd_tinydir.h" diff --git a/tools/merger/sfw_core.h.inl b/tools/merger/sfw_core.h.inl index 4488c0c..be218ee 100644 --- a/tools/merger/sfw_core.h.inl +++ b/tools/merger/sfw_core.h.inl @@ -36,6 +36,11 @@ //--STRIP {{FILE:sfw/core/typedefs.h}} +//--STRIP +//#include "core/int_types.h" +//#include "core/math_defs.h" +//--STRIP +{{FILE:sfw/core/marshalls.h}} //--STRIP //#include "core/int_types.h" diff --git a/tools/merger/sfw_full.cpp.inl b/tools/merger/sfw_full.cpp.inl index 64e42c2..42ef475 100644 --- a/tools/merger/sfw_full.cpp.inl +++ b/tools/merger/sfw_full.cpp.inl @@ -360,8 +360,6 @@ //--STRIP {{FILE:sfw/core/file_access.cpp}} -{{FILE:sfw/core/3rd_tinydir.h}} - //--STRIP //#include "dir_access.h" //#include "3rd_tinydir.h" diff --git a/tools/merger/sfw_full.h.inl b/tools/merger/sfw_full.h.inl index d66da56..d41f09d 100644 --- a/tools/merger/sfw_full.h.inl +++ b/tools/merger/sfw_full.h.inl @@ -36,6 +36,11 @@ //--STRIP {{FILE:sfw/core/typedefs.h}} +//--STRIP +//#include "core/int_types.h" +//#include "core/math_defs.h" +//--STRIP +{{FILE:sfw/core/marshalls.h}} //--STRIP //#include "core/int_types.h"