123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- /*
- Simple DirectMedia Layer
- Copyright (C) 1997-2024 Sam Lantinga <[email protected]>
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
- */
- #include "SDL_internal.h"
- #include "SDL_filesystem_c.h"
- #include "SDL_sysfilesystem.h"
- #include "../stdlib/SDL_sysstdlib.h"
- bool SDL_RemovePath(const char *path)
- {
- if (!path) {
- return SDL_InvalidParamError("path");
- }
- return SDL_SYS_RemovePath(path);
- }
- bool SDL_RenamePath(const char *oldpath, const char *newpath)
- {
- if (!oldpath) {
- return SDL_InvalidParamError("oldpath");
- } else if (!newpath) {
- return SDL_InvalidParamError("newpath");
- }
- return SDL_SYS_RenamePath(oldpath, newpath);
- }
- bool SDL_CopyFile(const char *oldpath, const char *newpath)
- {
- if (!oldpath) {
- return SDL_InvalidParamError("oldpath");
- } else if (!newpath) {
- return SDL_InvalidParamError("newpath");
- }
- return SDL_SYS_CopyFile(oldpath, newpath);
- }
- bool SDL_CreateDirectory(const char *path)
- {
- // TODO: Recursively create subdirectories
- if (!path) {
- return SDL_InvalidParamError("path");
- }
- return SDL_SYS_CreateDirectory(path);
- }
- bool SDL_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback callback, void *userdata)
- {
- if (!path) {
- return SDL_InvalidParamError("path");
- } else if (!callback) {
- return SDL_InvalidParamError("callback");
- }
- return SDL_SYS_EnumerateDirectory(path, path, callback, userdata);
- }
- bool SDL_GetPathInfo(const char *path, SDL_PathInfo *info)
- {
- SDL_PathInfo dummy;
- if (!info) {
- info = &dummy;
- }
- SDL_zerop(info);
- if (!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; // everything matches!
- }
- // this is just '*' and '?', with '/' matching nothing.
- 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 != '/')) { // end of string (checked at `while`) or path separator do not match '?'.
- sch = *(++str);
- pch = *(++pattern);
- } else if (!pattern_backtrack || (sch_backtrack == '/')) { // we didn't have a match. Are we in a '*' and NOT on a path separator? Keep going. Otherwise, fail.
- *matched_to_dir = false;
- return false;
- } else { // still here? Wasn't a match, but we're definitely in a '*' pattern.
- str = ++str_backtrack;
- pattern = pattern_backtrack;
- sch_backtrack = sch;
- sch = *str;
- pch = *pattern;
- }
- }
- // '*' at the end can be ignored, they are allowed to match nothing.
- while (pch == '*') {
- pch = *(++pattern);
- }
- *matched_to_dir = ((pch == '/') || (pch == '\0')); // end of string and the pattern is complete or failed at a '/'? We should descend into this directory.
- return (pch == '\0'); // survived the whole pattern? That's a match!
- }
- // Note that this will currently encode illegal codepoints: UTF-16 surrogates, 0xFFFE, and 0xFFFF.
- // and a codepoint > 0x10FFFF will fail the same as if there wasn't enough memory.
- // clean this up if you want to move this to SDL_string.c.
- static size_t EncodeCodepointToUtf8(char *ptr, Uint32 cp, size_t remaining)
- {
- if (cp < 0x80) { // fits in a single UTF-8 byte.
- if (remaining) {
- *ptr = (char) cp;
- return 1;
- }
- } else if (cp < 0x800) { // fits in 2 bytes.
- if (remaining >= 2) {
- ptr[0] = (char) ((cp >> 6) | 128 | 64);
- ptr[1] = (char) (cp & 0x3F) | 128;
- return 2;
- }
- } else if (cp < 0x10000) { // fits in 3 bytes.
- 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) { // fits in 4 bytes.
- 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); // lazy: just allocating the max needed.
- 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); // shrink it down.
- if (ptr) { // shouldn't fail, but if it does, `result` is still valid.
- 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 int SDLCALL GlobDirectoryCallback(void *userdata, const char *dirname, const char *fname)
- {
- SDL_assert(userdata != NULL);
- SDL_assert(dirname != NULL);
- SDL_assert(fname != NULL);
- //SDL_Log("GlobDirectoryCallback('%s', '%s')", dirname, fname);
- GlobDirCallbackData *data = (GlobDirCallbackData *) userdata;
- // !!! FIXME: if we're careful, we can keep a single buffer in `data` that we push and pop paths off the end of as we walk the tree,
- // !!! FIXME: and only casefold the new pieces instead of allocating and folding full paths for all of this.
- char *fullpath = NULL;
- if (SDL_asprintf(&fullpath, "%s/%s", dirname, fname) < 0) {
- return -1;
- }
- char *folded = NULL;
- if (data->flags & SDL_GLOB_CASEINSENSITIVE) {
- folded = CaseFoldUtf8String(fullpath);
- if (!folded) {
- return -1;
- }
- }
- bool matched_to_dir = false;
- const bool matched = data->matcher(data->pattern, (folded ? folded : fullpath) + data->basedirlen, &matched_to_dir);
- //SDL_Log("GlobDirectoryCallback: Considered %spath='%s' vs pattern='%s': %smatched (matched_to_dir=%s)", folded ? "(folded) " : "", (folded ? folded : fullpath) + data->basedirlen, data->pattern, matched ? "" : "NOT ", matched_to_dir ? "TRUE" : "FALSE");
- 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 -1; // stop enumerating, return failure to the app.
- }
- data->num_entries++;
- }
- int result = 1; // keep enumerating by default.
- if (matched_to_dir) {
- SDL_PathInfo info;
- if (data->getpathinfo(fullpath, &info, data->fsuserdata) && (info.type == SDL_PATHTYPE_DIRECTORY)) {
- //SDL_Log("GlobDirectoryCallback: Descending into subdir '%s'", fname);
- if (!data->enumerator(fullpath, GlobDirectoryCallback, data, data->fsuserdata)) {
- result = -1;
- }
- }
- }
- 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;
- if (!path) {
- SDL_InvalidParamError("path");
- return NULL;
- }
- // if path ends with any '/', chop them off, so we don't confuse the pattern matcher later.
- char *pathcpy = NULL;
- size_t pathlen = SDL_strlen(path);
- if (pathlen && (path[pathlen-1] == '/')) {
- pathcpy = SDL_strdup(path);
- if (!pathcpy) {
- return NULL;
- }
- char *ptr = &pathcpy[pathlen-1];
- while ((ptr >= pathcpy) && (*ptr == '/')) {
- *(ptr--) = '\0';
- }
- path = pathcpy;
- }
- if (!pattern) {
- flags &= ~SDL_GLOB_CASEINSENSITIVE; // avoid some unnecessary allocations and work later.
- }
- 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; // no pattern? Everything matches.
- // !!! FIXME
- //} else if (flags & SDL_GLOB_GITIGNORE) {
- // data.matcher = GitIgnoreMatch;
- } else {
- data.matcher = WildcardMatch;
- }
- data.pattern = folded ? folded : pattern;
- data.flags = flags;
- data.enumerator = enumerator;
- data.getpathinfo = getpathinfo;
- data.fsuserdata = userdata;
- data.basedirlen = SDL_strlen(path) + 1; // +1 for the '/' we'll be adding.
- 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 *)); // +1 for NULL terminator at end of array.
- 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); // this should never fail for a memory stream!
- char *ptr = (char *) (result + (data.num_entries + 1));
- iorc = SDL_ReadIO(data.string_stream, ptr, streamlen);
- SDL_assert(iorc == (Sint64) streamlen); // this should never fail for a memory stream!
- for (int i = 0; i < data.num_entries; i++) {
- result[i] = ptr;
- ptr += SDL_strlen(ptr) + 1;
- }
- }
- result[data.num_entries] = NULL; // NULL terminate the list.
- *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)
- {
- //SDL_Log("SDL_GlobDirectory('%s', '%s') ...", path, pattern);
- 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;
- if ((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)
- {
- char *path = SDL_SYS_GetPrefPath(org, app);
- return path;
- }
- 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;
- }
- }
- }
|