mirror of
https://github.com/libsdl-org/SDL.git
synced 2025-09-30 15:08:31 +00:00
filesystem: Added SDL_GlobDirectory() and SDL_GlobStorageDirectory().
Fixes #9287.
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "SDL_internal.h"
|
||||
#include "SDL_sysfilesystem.h"
|
||||
#include "../stdlib/SDL_sysstdlib.h"
|
||||
|
||||
int SDL_RemovePath(const char *path)
|
||||
{
|
||||
@@ -74,3 +75,279 @@ int SDL_GetPathInfo(const char *path, SDL_PathInfo *info)
|
||||
|
||||
return SDL_SYS_GetPathInfo(path, info);
|
||||
}
|
||||
|
||||
static SDL_bool EverythingMatch(const char *pattern, const char *str, SDL_bool *matched_to_dir)
|
||||
{
|
||||
SDL_assert(pattern == NULL);
|
||||
SDL_assert(str != NULL);
|
||||
SDL_assert(matched_to_dir != NULL);
|
||||
|
||||
*matched_to_dir = SDL_TRUE;
|
||||
return SDL_TRUE; // everything matches!
|
||||
}
|
||||
|
||||
// this is just '*' and '?', with '/' matching nothing.
|
||||
static SDL_bool WildcardMatch(const char *pattern, const char *str, SDL_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 = SDL_FALSE;
|
||||
return SDL_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!
|
||||
}
|
||||
|
||||
static char *CaseFoldUtf8String(const char *fname)
|
||||
{
|
||||
SDL_assert(fname != NULL);
|
||||
const size_t allocation = (SDL_strlen(fname) + 1) * 3;
|
||||
char *retval = (char *) SDL_malloc(allocation); // lazy: just allocating the max needed.
|
||||
if (!retval) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Uint32 codepoint;
|
||||
size_t written = 0;
|
||||
while ((codepoint = SDL_StepUTF8(&fname, 4)) != 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(written < allocation);
|
||||
retval[written++] = folded[i];
|
||||
}
|
||||
}
|
||||
|
||||
SDL_assert(written < allocation);
|
||||
retval[written++] = '\0';
|
||||
|
||||
if (written < allocation) {
|
||||
void *ptr = SDL_realloc(retval, written); // shrink it down.
|
||||
if (ptr) { // shouldn't fail, but if it does, `retval` is still valid.
|
||||
retval = (char *) ptr;
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
typedef struct GlobDirCallbackData
|
||||
{
|
||||
SDL_bool (*matcher)(const char *pattern, const char *str, SDL_bool *matched_to_dir);
|
||||
const char *pattern;
|
||||
int num_entries;
|
||||
Uint32 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_GLOBDIR_CASEINSENSITIVE) {
|
||||
folded = CaseFoldUtf8String(fullpath);
|
||||
if (!folded) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_bool matched_to_dir = SDL_FALSE;
|
||||
const SDL_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 retval = 1; // keep enumerating by default.
|
||||
if (matched_to_dir) {
|
||||
SDL_PathInfo info;
|
||||
if ((data->getpathinfo(fullpath, &info, data->fsuserdata) == 0) && (info.type == SDL_PATHTYPE_DIRECTORY)) {
|
||||
//SDL_Log("GlobDirectoryCallback: Descending into subdir '%s'", fname);
|
||||
if (data->enumerator(fullpath, GlobDirectoryCallback, data, data->fsuserdata) < 0) {
|
||||
retval = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_free(fullpath);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
char **SDL_InternalGlobDirectory(const char *path, const char *pattern, Uint32 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;
|
||||
}
|
||||
|
||||
char *folded = NULL;
|
||||
if (pattern && (flags & SDL_GLOBDIR_CASEINSENSITIVE)) {
|
||||
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_GLOBDIR_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 **retval = NULL;
|
||||
if (data.enumerator(path, GlobDirectoryCallback, &data, data.fsuserdata) == 0) {
|
||||
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.
|
||||
retval = (char **) SDL_malloc(buflen);
|
||||
if (retval) {
|
||||
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 *) (retval + (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++) {
|
||||
retval[i] = ptr;
|
||||
ptr += SDL_strlen(ptr) + 1;
|
||||
}
|
||||
}
|
||||
retval[data.num_entries] = NULL; // NULL terminate the list.
|
||||
*count = data.num_entries;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_CloseIO(data.string_stream);
|
||||
SDL_free(folded);
|
||||
SDL_free(pathcpy);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int GlobDirectoryGetPathInfo(const char *path, SDL_PathInfo *info, void *userdata)
|
||||
{
|
||||
return SDL_GetPathInfo(path, info);
|
||||
}
|
||||
|
||||
static int 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, Uint32 flags, int *count)
|
||||
{
|
||||
//SDL_Log("SDL_GlobDirectory('%s', '%s') ...", path, pattern);
|
||||
return SDL_InternalGlobDirectory(path, pattern, flags, count, GlobDirectoryEnumerator, GlobDirectoryGetPathInfo, NULL);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user