Took the DirAccess and FileAccess implementations from pandemonium. Also kept a few helper methods from the old api. Removed tinydir.

This commit is contained in:
Relintai 2024-01-13 11:54:15 +01:00
parent 9ee7156890
commit 42d9e1be66
10 changed files with 2004 additions and 1026 deletions

View File

@ -1,831 +0,0 @@
/*
Copyright (c) 2013-2019, tinydir authors:
- Cong Xu
- Lautis Sun
- Baudouin Feildel
- Andargor <andargor@yahoo.com>
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 <errno.h>
#include <stdlib.h>
#include <string.h>
#ifdef _MSC_VER
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <windows.h>
# include <tchar.h>
# pragma warning(push)
# pragma warning (disable : 4996)
#else
# include <dirent.h>
# include <libgen.h>
# include <sys/stat.h>
# include <stddef.h>
#endif
#ifdef __MINGW32__
# include <tchar.h>
#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 <windows.h>
# define _TINYDIR_PATH_MAX MAX_PATH
#elif defined __linux__
# include <limits.h>
# ifdef PATH_MAX
# define _TINYDIR_PATH_MAX PATH_MAX
# endif
#elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
# include <sys/param.h>
# if defined(BSD)
# include <limits.h>
# 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 <unistd.h>
#endif
#if _BSD_SOURCE || _SVID_SOURCE || \
(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
# define _TINYDIR_HAS_DIRFD
# include <sys/types.h>
#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 <ben@decadent.org.uk>
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

View File

@ -2,144 +2,811 @@
//--STRIP
#include "dir_access.h"
#include "3rd_tinydir.h"
#include <cstdio>
//--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 <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/statvfs.h>
#ifdef HAVE_MNTENT
#include <mntent.h>
#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<String> *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<String> list;
_get_drives(&list);
return list.size();
}
String DirAccess::get_drive(int p_drive) {
List<String> 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<String> dirs;
List<String> 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<String>::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<String>::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<String> 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<uint8_t> 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<String> 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<String>::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 == "..";
}

View File

@ -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

View File

@ -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 <cstdio>
//--STRIP
#if defined(_WIN64) || defined(_WIN32)
#else
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
String FileAccess::read_file(const String &path) {
FILE *f = fopen(path.utf8().get_data(), "r");
#include <errno.h>
ERR_FAIL_COND_V_MSG(!f, String(), "Error opening file! " + path);
#include <unistd.h>
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 <io.h>
#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 <fcntl.h>
#else
#include <sys/ioctl.h>
#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<uint8_t> 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<uint8_t> 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<uint8_t> *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<uint8_t> &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<char> 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<String> FileAccess::get_csv_line(const String &p_delim) const {
ERR_FAIL_COND_V_MSG(p_delim.length() != 1, Vector<String>(), "Only single character delimiters are supported to parse CSV lines.");
ERR_FAIL_COND_V_MSG(p_delim[0] == '"', Vector<String>(), "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<String> 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<uint8_t> 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<String> &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<uint8_t> &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<uint8_t> 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<uint8_t>();
}
ERR_FAIL_V_MSG(Vector<uint8_t>(), "Can't open file from path '" + String(p_path) + "'.");
}
Vector<uint8_t> 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<uint8_t> 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;
};
*/

View File

@ -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<uint8_t> read_file_bin(const String &path);
Error read_file_into_bin(const String &path, Vector<uint8_t> *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<uint8_t> &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<String> 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<String> &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<uint8_t> &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<uint8_t> 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

142
sfw/core/marshalls.h Normal file
View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"