Skip to content

Commit

Permalink
Added directory iterators.
Browse files Browse the repository at this point in the history
Stream types may provide their own directory iterators. Currently
implemented for both file and resource streams.
  • Loading branch information
akb825 committed Jan 4, 2025
1 parent 10093cd commit e60f3dd
Show file tree
Hide file tree
Showing 13 changed files with 570 additions and 65 deletions.
17 changes: 1 addition & 16 deletions modules/Core/Core/include/DeepSea/Core/Streams/Endian.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,7 @@ extern "C"

#if DS_MSC
// Assume always little endian.

/**
* @brief Macro set to 1 for big-endian CPUs, 0 for little-endian CPUs.
*/
#define DS_BIG_ENDIAN 0

/**
* @brief Macro set to 1 for big-endian CPUs, 0 for little-endian CPUs.
*/
#define DS_LITTLE_ENDIAN 1
#else
#if !defined(__BYTE_ORDER__)
Expand All @@ -58,18 +50,11 @@ extern "C"
#define DS_BIG_ENDIAN 1

/**
* @brief Macro set to 1 for big-endian CPUs, 0 for little-endian CPUs.
* @brief Macro set to 1 for little-endian CPUs, 0 for big-endian CPUs.
*/
#define DS_LITTLE_ENDIAN 0
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
/**
* @brief Macro set to 1 for big-endian CPUs, 0 for little-endian CPUs.
*/
#define DS_BIG_ENDIAN 0

/**
* @brief Macro set to 1 for big-endian CPUs, 0 for little-endian CPUs.
*/
#define DS_LITTLE_ENDIAN 1
#else
#error Unsupported byte ordering.
Expand Down
29 changes: 29 additions & 0 deletions modules/Core/Core/include/DeepSea/Core/Streams/FileStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,35 @@ extern "C"
* @see dsFileStream
*/

/**
* @brief Starts iterating over a directory on the filesystem.
* @remark errno will be set on failure.
* @param path The path to the directory.
* @return The directory iterator or NULL if the directory cannot be iterated.
*/
DS_CORE_EXPORT dsDirectoryIterator dsFileStream_openDirectory(const char* path);

/**
* @brief Gets the next entry in a directory.
*
* The . and .. entries will be implicitly skipped.
*
* @remark errno will be set on failure.
* @param[out] outEntry The entry to populate.
* @param iterator The iterator to get the next entry with.
* @return The result of getting the next entry.
*/
DS_CORE_EXPORT dsDirectoryEntryResult dsFileStream_nextDirectoryEntry(
dsDirectoryEntry* outEntry, dsDirectoryIterator iterator);

/**
* @brief Closes a directory.
* @remark errno will be set on failure.
* @param iterator The directory iterator to close.
* @return False if the directory couldn't be closed.
*/
DS_CORE_EXPORT bool dsFileStream_closeDirectory(dsDirectoryIterator iterator);

/**
* @brief Opens a file stream with a file path.
* @remark errno will be set on failure.
Expand Down
33 changes: 33 additions & 0 deletions modules/Core/Core/include/DeepSea/Core/Streams/ResourceStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,39 @@ DS_CORE_EXPORT const char* dsResourceStream_getDirectory(dsFileResourceType type
DS_CORE_EXPORT bool dsResourceStream_getPath(char* outResult, size_t resultSize,
dsFileResourceType type, const char* path);

/**
* @brief Starts iterating over a directory from a resource.
* @remark errno will be set on failure.
* @param type The resource type.
* @param path The path to the directory.
* @return The directory iterator or NULL if the directory cannot be iterated.
*/
DS_CORE_EXPORT dsDirectoryIterator dsResourceStream_openDirectory(
dsFileResourceType type, const char* path);

/**
* @brief Gets the next entry in a directory.
*
* The . and .. entries will be implicitly skipped.
*
* @remark When listing a directory for an embedded path on Android, due to limitations of the
* NDK only file entries will be listed.
* @remark errno will be set on failure.
* @param[out] outEntry The entry to populate.
* @param iterator The iterator to get the next entry with.
* @return The result of getting the next entry.
*/
DS_CORE_EXPORT dsDirectoryEntryResult dsResourceStream_nextDirectoryEntry(
dsDirectoryEntry* outEntry, dsDirectoryIterator iterator);

/**
* @brief Closes a directory.
* @remark errno will be set on failure.
* @param iterator The directory iterator to close.
* @return False if the directory couldn't be closed.
*/
DS_CORE_EXPORT bool dsResourceStream_closeDirectory(dsDirectoryIterator iterator);

/**
* @brief Opens a stream for a resource.
* @remark errno will be set on failure.
Expand Down
46 changes: 45 additions & 1 deletion modules/Core/Core/include/DeepSea/Core/Streams/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,30 @@ extern "C"

#if DS_WINDOWS
#define DS_PATH_MAX (_MAX_PATH + 1)
#define DS_FILE_NAME_MAX DS_PATH_MAX
#define DS_PATH_SEPARATOR '\\'
#define DS_PATH_ALT_SEPARATOR '/'
#else
/**
* @brief Define for the typical maximum length of a path.
*
* There are cases on some filesystems where the length can exceed this path, but this should be
* There are cases on some filesystems where the length can exceed this limit, but this should be
* sufficient for typical cases.
*
* This includes space for the NULL terminator at the end of the string.
*/
#define DS_PATH_MAX (PATH_MAX + 1)

/**
* @brief Define for the typical maximum length of a file name.
*
* There are cases on some filesystems where the length can exceed this limit, but this should be
* sufficient for typical cases.
*
* This includes space for the NULL terminator at the end of the string.
*/
#define DS_FILE_NAME_MAX (NAME_MAX + 1)

/**
* @brief The main path separator for the current platform.
*/
Expand Down Expand Up @@ -112,6 +123,16 @@ typedef enum dsFileStatus
dsFileStatus_ExistsDirectory ///< File exists as a directory.
} dsFileStatus;

/**
* @brief Enum for the result of retrieving a directory entry.
*/
typedef enum dsDirectoryEntryResult
{
dsDirectoryEntryResult_Success, ///< The directory entry was successfully retrieved.
dsDirectoryEntryResult_End, ///< The end of the directory was reached.
dsDirectoryEntryResult_Error ///< An error occurred when getting the entry.
} dsDirectoryEntryResult;

/**
* @brief Function for reading from a stream.
* @param stream The stream to read from.
Expand Down Expand Up @@ -341,6 +362,29 @@ typedef struct dsResourceStream
bool isFile;
} dsResourceStream;

/**
* @brief Opque type for an iterator over a directory.
*
* It is only valid to use a dsDirectoryIterator instance with the stream type that created it.
*/
typedef void* dsDirectoryIterator;

/**
* @brief Structure that defines an entry within a directory.
*/
typedef struct dsDirectoryEntry
{
/**
* @brief Whether this entry is itself a directory.
*/
bool isDirectory;

/**
* @brief The name of the entry.
*/
char name[DS_FILE_NAME_MAX];
} dsDirectoryEntry;

#ifdef __cplusplus
}
#endif
147 changes: 147 additions & 0 deletions modules/Core/Core/src/Streams/FileStream.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,49 @@
#define _FILE_OFFSET_BITS 64

#include <DeepSea/Core/Streams/FileStream.h>
#include <DeepSea/Core/Streams/Path.h>
#include <DeepSea/Core/Assert.h>
#include <DeepSea/Core/Error.h>

#include <string.h>

#if DS_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <malloc.h>
#else
#include <dirent.h>
#endif

#if DS_WINDOWS
typedef struct dsDirectoryIteratorInfo
{
HANDLE handle;
bool alreadyRetrieved;
WIN32_FIND_DATAA findData;
} dsDirectoryIteratorInfo;

static void setErrno(void)
{
switch (GetLastError())
{
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
errno = ENOENT;
break;
case ERROR_ACCESS_DENIED:
errno = EACCES;
break;
case ERROR_NOT_ENOUGH_MEMORY:
errno = ENOMEM;
break;
default:
errno = EIO;
break;
}
}
#endif

static void initFileStream(dsFileStream* stream, FILE* file)
{
dsStream* baseStream = (dsStream*)stream;
Expand All @@ -35,6 +75,113 @@ static void initFileStream(dsFileStream* stream, FILE* file)
stream->file = file;
}

dsDirectoryIterator dsFileStream_openDirectory(const char* path)
{
if (!path || *path == 0)
{
errno = EINVAL;
return NULL;
}

#if DS_WINDOWS
char searchPath[DS_PATH_MAX];
if (!dsPath_combine(searchPath, sizeof(searchPath), path, "*"))
return NULL;

dsDirectoryIteratorInfo* info = malloc(sizeof(dsDirectoryIteratorInfo));
if (!info)
return NULL;

info->handle = FindFirstFileEx(searchPath, FindExInfoBasic, &info->findData,
FindExSearchNameMatch, NULL, 0);
if (!info->handle)
{
free(info);
setErrno();
return NULL;
}

info->alreadyRetrieved = true;
return info;
#else
return opendir(path);
#endif
}

dsDirectoryEntryResult dsFileStream_nextDirectoryEntry(
dsDirectoryEntry* outEntry, dsDirectoryIterator iterator)
{
if (!outEntry || !iterator)
{
errno = EINVAL;
return dsDirectoryEntryResult_Error;
}

char* entryName;
#if DS_WINDOWS
dsDirectoryIteratorInfo* info = (dsDirectoryIteratorInfo*)iterator;
if (info->alreadyRetrieved)
info->alredyRetrieved = false;
else if (!FindNextFile(info->handle, &info->findData))
{
if (GetLastError() == ERROR_NO_MORE_FILES)
return dsDirectoryEntryResult_End;

setErrno();
return NULL;
}

entryName = info->cFileName;
outEntry->isDirectory = (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
#else
errno = 0;
do
{
struct dirent* dirEntry = readdir((DIR*)iterator);
if (!dirEntry)
{
if (errno == 0)
return dsDirectoryEntryResult_End;
return dsDirectoryEntryResult_Error;
}

entryName = dirEntry->d_name;
outEntry->isDirectory = dirEntry->d_type == DT_DIR;
} while (strcmp(entryName, ".") == 0 || strcmp(entryName, "..") == 0);
#endif

size_t nameLen = strlen(entryName) + 1;
if (nameLen > DS_FILE_NAME_MAX)
{
memcpy(outEntry->name, entryName, DS_FILE_NAME_MAX - 1);
outEntry->name[DS_FILE_NAME_MAX - 1] = 0;
}
else
memcpy(outEntry->name, entryName, nameLen);
return dsDirectoryEntryResult_Success;
}

bool dsFileStream_closeDirectory(dsDirectoryIterator iterator)
{
if (!iterator)
{
errno = EINVAL;
return false;
}

#if DS_WINDOWS
dsDirectoryIteratorInfo* info = (dsDirectoryIteratorInfo*)iterator;
bool success = FindClose(info->handle);
if (success)
free(info);
else
setErrno();
return success;
#else
return closedir((DIR*)iterator) == 0;
#endif
}

bool dsFileStream_openPath(dsFileStream* stream, const char* filePath,
const char* mode)
{
Expand Down
Loading

0 comments on commit e60f3dd

Please sign in to comment.