#include "SDL_internal.h"
#include "SDL_filesystem_c.h"
#include "SDL_sysfilesystem.h"
#include "../stdlib/SDL_sysstdlib.h"
bool SDL_RemovePath(const char *path)
{
CHECK_PARAM(!path) {
return SDL_InvalidParamError("path");
}
return SDL_SYS_RemovePath(path);
}
bool SDL_RenamePath(const char *oldpath, const char *newpath)
{
CHECK_PARAM(!oldpath) {
return SDL_InvalidParamError("oldpath");
}
CHECK_PARAM(!newpath) {
return SDL_InvalidParamError("newpath");
}
return SDL_SYS_RenamePath(oldpath, newpath);
}
bool SDL_CopyFile(const char *oldpath, const char *newpath)
{
CHECK_PARAM(!oldpath) {
return SDL_InvalidParamError("oldpath");
}
CHECK_PARAM(!newpath) {
return SDL_InvalidParamError("newpath");
}
return SDL_SYS_CopyFile(oldpath, newpath);
}
bool SDL_CreateDirectory(const char *path)
{
CHECK_PARAM(!path) {
return SDL_InvalidParamError("path");
}
bool retval = SDL_SYS_CreateDirectory(path);
if (!retval && *path) { char *parents = SDL_strdup(path);
if (!parents) {
return false; }
const size_t slen = SDL_strlen(parents);
#ifdef SDL_PLATFORM_WINDOWS
if ((parents[slen - 1] == '/') || (parents[slen - 1] == '\\'))
#else
if (parents[slen - 1] == '/')
#endif
{
parents[slen - 1] = '\0';
retval = SDL_SYS_CreateDirectory(parents);
}
if (!retval) {
for (char *ptr = parents; *ptr; ptr++) {
const char ch = *ptr;
#ifdef SDL_PLATFORM_WINDOWS
const bool issep = (ch == '/') || (ch == '\\');
if (issep && ((ptr - parents) == 2) && (parents[1] == ':')) {
continue; }
#else
const bool issep = (ch == '/');
if (issep && ((ptr - parents) == 0)) {
continue; }
#endif
if (issep) {
*ptr = '\0';
retval = SDL_SYS_CreateDirectory(parents);
if (!retval) { break;
}
*ptr = ch;
}
}
retval = SDL_SYS_CreateDirectory(parents);
}
SDL_free(parents);
}
return retval;
}
bool SDL_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback callback, void *userdata)
{
CHECK_PARAM(!path) {
return SDL_InvalidParamError("path");
}
CHECK_PARAM(!callback) {
return SDL_InvalidParamError("callback");
}
return SDL_SYS_EnumerateDirectory(path, callback, userdata);
}
bool SDL_GetPathInfo(const char *path, SDL_PathInfo *info)
{
SDL_PathInfo dummy;
if (!info) {
info = &dummy;
}
SDL_zerop(info);
CHECK_PARAM(!path) {
return SDL_InvalidParamError("path");
}
return SDL_SYS_GetPathInfo(path, info);
}
static bool EverythingMatch(const char *pattern, const char *str, bool *matched_to_dir)
{
SDL_assert(pattern == NULL);
SDL_assert(str != NULL);
SDL_assert(matched_to_dir != NULL);
*matched_to_dir = true;
return true; }
static bool WildcardMatch(const char *pattern, const char *str, bool *matched_to_dir)
{
SDL_assert(pattern != NULL);
SDL_assert(str != NULL);
SDL_assert(matched_to_dir != NULL);
const char *str_backtrack = NULL;
const char *pattern_backtrack = NULL;
char sch_backtrack = 0;
char sch = *str;
char pch = *pattern;
while (sch) {
if (pch == '*') {
str_backtrack = str;
pattern_backtrack = ++pattern;
sch_backtrack = sch;
pch = *pattern;
} else if (pch == sch) {
if (pch == '/') {
str_backtrack = pattern_backtrack = NULL;
}
sch = *(++str);
pch = *(++pattern);
} else if ((pch == '?') && (sch != '/')) { sch = *(++str);
pch = *(++pattern);
} else if (!pattern_backtrack || (sch_backtrack == '/')) { *matched_to_dir = false;
return false;
} else { str = ++str_backtrack;
pattern = pattern_backtrack;
sch_backtrack = sch;
sch = *str;
pch = *pattern;
}
#ifdef SDL_PLATFORM_WINDOWS
if (sch == '\\') {
sch = '/';
}
#endif
}
while (pch == '*') {
pch = *(++pattern);
}
*matched_to_dir = (pch == '/');
return (pch == '\0'); }
static size_t EncodeCodepointToUtf8(char *ptr, Uint32 cp, size_t remaining)
{
if (cp < 0x80) { if (remaining) {
*ptr = (char) cp;
return 1;
}
} else if (cp < 0x800) { if (remaining >= 2) {
ptr[0] = (char) ((cp >> 6) | 128 | 64);
ptr[1] = (char) (cp & 0x3F) | 128;
return 2;
}
} else if (cp < 0x10000) { if (remaining >= 3) {
ptr[0] = (char) ((cp >> 12) | 128 | 64 | 32);
ptr[1] = (char) ((cp >> 6) & 0x3F) | 128;
ptr[2] = (char) (cp & 0x3F) | 128;
return 3;
}
} else if (cp <= 0x10FFFF) { if (remaining >= 4) {
ptr[0] = (char) ((cp >> 18) | 128 | 64 | 32 | 16);
ptr[1] = (char) ((cp >> 12) & 0x3F) | 128;
ptr[2] = (char) ((cp >> 6) & 0x3F) | 128;
ptr[3] = (char) (cp & 0x3F) | 128;
return 4;
}
}
return 0;
}
static char *CaseFoldUtf8String(const char *fname)
{
SDL_assert(fname != NULL);
const size_t allocation = (SDL_strlen(fname) + 1) * 3 * 4;
char *result = (char *) SDL_malloc(allocation); if (!result) {
return NULL;
}
Uint32 codepoint;
char *ptr = result;
size_t remaining = allocation;
while ((codepoint = SDL_StepUTF8(&fname, NULL)) != 0) {
Uint32 folded[3];
const int num_folded = SDL_CaseFoldUnicode(codepoint, folded);
SDL_assert(num_folded > 0);
SDL_assert(num_folded <= SDL_arraysize(folded));
for (int i = 0; i < num_folded; i++) {
SDL_assert(remaining > 0);
const size_t rc = EncodeCodepointToUtf8(ptr, folded[i], remaining);
SDL_assert(rc > 0);
SDL_assert(rc < remaining);
remaining -= rc;
ptr += rc;
}
}
SDL_assert(remaining > 0);
remaining--;
*ptr = '\0';
if (remaining > 0) {
SDL_assert(allocation > remaining);
ptr = (char *)SDL_realloc(result, allocation - remaining); if (ptr) { result = ptr;
}
}
return result;
}
typedef struct GlobDirCallbackData
{
bool (*matcher)(const char *pattern, const char *str, bool *matched_to_dir);
const char *pattern;
int num_entries;
SDL_GlobFlags flags;
SDL_GlobEnumeratorFunc enumerator;
SDL_GlobGetPathInfoFunc getpathinfo;
void *fsuserdata;
size_t basedirlen;
SDL_IOStream *string_stream;
} GlobDirCallbackData;
static SDL_EnumerationResult SDLCALL GlobDirectoryCallback(void *userdata, const char *dirname, const char *fname)
{
SDL_assert(userdata != NULL);
SDL_assert(dirname != NULL);
SDL_assert(fname != NULL);
GlobDirCallbackData *data = (GlobDirCallbackData *) userdata;
char *fullpath = NULL;
if (SDL_asprintf(&fullpath, "%s%s", dirname, fname) < 0) {
return SDL_ENUM_FAILURE;
}
char *folded = NULL;
if (data->flags & SDL_GLOB_CASEINSENSITIVE) {
folded = CaseFoldUtf8String(fullpath);
if (!folded) {
return SDL_ENUM_FAILURE;
}
}
bool matched_to_dir = false;
const bool matched = data->matcher(data->pattern, (folded ? folded : fullpath) + data->basedirlen, &matched_to_dir);
SDL_free(folded);
if (matched) {
const char *subpath = fullpath + data->basedirlen;
const size_t slen = SDL_strlen(subpath) + 1;
if (SDL_WriteIO(data->string_stream, subpath, slen) != slen) {
SDL_free(fullpath);
return SDL_ENUM_FAILURE; }
data->num_entries++;
}
SDL_EnumerationResult result = SDL_ENUM_CONTINUE; if (matched_to_dir) {
SDL_PathInfo info;
if (data->getpathinfo(fullpath, &info, data->fsuserdata) && (info.type == SDL_PATHTYPE_DIRECTORY)) {
if (!data->enumerator(fullpath, GlobDirectoryCallback, data, data->fsuserdata)) {
result = SDL_ENUM_FAILURE;
}
}
}
SDL_free(fullpath);
return result;
}
char **SDL_InternalGlobDirectory(const char *path, const char *pattern, SDL_GlobFlags flags, int *count, SDL_GlobEnumeratorFunc enumerator, SDL_GlobGetPathInfoFunc getpathinfo, void *userdata)
{
int dummycount;
if (!count) {
count = &dummycount;
}
*count = 0;
CHECK_PARAM(!path) {
SDL_InvalidParamError("path");
return NULL;
}
char *pathcpy = NULL;
size_t pathlen = SDL_strlen(path);
if ((pathlen > 1) && ((path[pathlen-1] == '/') || (path[pathlen-1] == '\\'))) {
pathcpy = SDL_strdup(path);
if (!pathcpy) {
return NULL;
}
char *ptr = &pathcpy[pathlen-1];
while ((ptr > pathcpy) && ((*ptr == '/') || (*ptr == '\\'))) {
*(ptr--) = '\0';
--pathlen;
}
path = pathcpy;
}
if (!pattern) {
flags &= ~SDL_GLOB_CASEINSENSITIVE; }
char *folded = NULL;
if (flags & SDL_GLOB_CASEINSENSITIVE) {
SDL_assert(pattern != NULL);
folded = CaseFoldUtf8String(pattern);
if (!folded) {
SDL_free(pathcpy);
return NULL;
}
}
GlobDirCallbackData data;
SDL_zero(data);
data.string_stream = SDL_IOFromDynamicMem();
if (!data.string_stream) {
SDL_free(folded);
SDL_free(pathcpy);
return NULL;
}
if (!pattern) {
data.matcher = EverythingMatch;
} else {
data.matcher = WildcardMatch;
}
data.pattern = folded ? folded : pattern;
data.flags = flags;
data.enumerator = enumerator;
data.getpathinfo = getpathinfo;
data.fsuserdata = userdata;
data.basedirlen = 0;
if (*path) {
if (SDL_strcmp(path, "/") == 0 || SDL_strcmp(path, "\\") == 0) {
data.basedirlen = 1;
} else {
data.basedirlen = pathlen + 1; }
}
char **result = NULL;
if (data.enumerator(path, GlobDirectoryCallback, &data, data.fsuserdata)) {
const size_t streamlen = (size_t) SDL_GetIOSize(data.string_stream);
const size_t buflen = streamlen + ((data.num_entries + 1) * sizeof (char *)); result = (char **) SDL_malloc(buflen);
if (result) {
if (data.num_entries > 0) {
Sint64 iorc = SDL_SeekIO(data.string_stream, 0, SDL_IO_SEEK_SET);
SDL_assert(iorc == 0); char *ptr = (char *) (result + (data.num_entries + 1));
iorc = SDL_ReadIO(data.string_stream, ptr, streamlen);
SDL_assert(iorc == (Sint64) streamlen); for (int i = 0; i < data.num_entries; i++) {
result[i] = ptr;
ptr += SDL_strlen(ptr) + 1;
}
}
result[data.num_entries] = NULL; *count = data.num_entries;
}
}
SDL_CloseIO(data.string_stream);
SDL_free(folded);
SDL_free(pathcpy);
return result;
}
static bool GlobDirectoryGetPathInfo(const char *path, SDL_PathInfo *info, void *userdata)
{
return SDL_GetPathInfo(path, info);
}
static bool GlobDirectoryEnumerator(const char *path, SDL_EnumerateDirectoryCallback cb, void *cbuserdata, void *userdata)
{
return SDL_EnumerateDirectory(path, cb, cbuserdata);
}
char **SDL_GlobDirectory(const char *path, const char *pattern, SDL_GlobFlags flags, int *count)
{
return SDL_InternalGlobDirectory(path, pattern, flags, count, GlobDirectoryEnumerator, GlobDirectoryGetPathInfo, NULL);
}
static char *CachedBasePath = NULL;
const char *SDL_GetBasePath(void)
{
if (!CachedBasePath) {
CachedBasePath = SDL_SYS_GetBasePath();
}
return CachedBasePath;
}
static char *CachedUserFolders[SDL_FOLDER_COUNT];
const char *SDL_GetUserFolder(SDL_Folder folder)
{
const int idx = (int) folder;
CHECK_PARAM((idx < 0) || (idx >= SDL_arraysize(CachedUserFolders))) {
SDL_InvalidParamError("folder");
return NULL;
}
if (!CachedUserFolders[idx]) {
CachedUserFolders[idx] = SDL_SYS_GetUserFolder(folder);
}
return CachedUserFolders[idx];
}
char *SDL_GetPrefPath(const char *org, const char *app)
{
CHECK_PARAM(!app) {
SDL_InvalidParamError("app");
return NULL;
}
if (!org) {
org = "";
}
return SDL_SYS_GetPrefPath(org, app);
}
char *SDL_GetCurrentDirectory(void)
{
return SDL_SYS_GetCurrentDirectory();
}
void SDL_InitFilesystem(void)
{
}
void SDL_QuitFilesystem(void)
{
if (CachedBasePath) {
SDL_free(CachedBasePath);
CachedBasePath = NULL;
}
for (int i = 0; i < SDL_arraysize(CachedUserFolders); i++) {
if (CachedUserFolders[i]) {
SDL_free(CachedUserFolders[i]);
CachedUserFolders[i] = NULL;
}
}
}