X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=dep%2Frandygaul%2Fcute_files.h;fp=dep%2Frandygaul%2Fcute_files.h;h=fcd585e9173227982094a31d73c5045b2ba7e356;hb=304b511d1a21194944b88ce739cd6a14755c2d3d;hp=0000000000000000000000000000000000000000;hpb=a00e095e762b56ffcfe9d5fc5ef7e4c597df765d;p=vg.git diff --git a/dep/randygaul/cute_files.h b/dep/randygaul/cute_files.h new file mode 100644 index 0000000..fcd585e --- /dev/null +++ b/dep/randygaul/cute_files.h @@ -0,0 +1,520 @@ +/* + ------------------------------------------------------------------------------ + Licensing information can be found at the end of the file. + ------------------------------------------------------------------------------ + + cute_files.h - v1.0 + + To create implementation (the function definitions) + #define CUTE_FILES_IMPLEMENTATION + in *one* C/CPP file (translation unit) that includes this file + + Summary: + Utility header for traversing directories to apply a function on each found file. + Recursively finds sub-directories. Can also be used to iterate over files in a + folder manually. All operations done in a cross-platform manner (thx posix!). + + This header does no dynamic memory allocation, and performs internally safe string + copies as necessary. Strings for paths, file names and file extensions are all + capped, and intended to use primarily the C run-time stack memory. Feel free to + modify the defines in this file to adjust string size limitations. + + Read the header for specifics on each function. + + Here's an example to print all files in a folder: + cf_dir_t dir; + cf_dir_open(&dir, "a"); + + while (dir.has_next) + { + cf_file_t file; + cf_read_file(&dir, &file); + printf("%s\n", file.name); + cf_dir_next(&dir); + } + + cf_dir_close(&dir); +*/ + +#if !defined(CUTE_FILES_H) + +#define CUTE_FILES_WINDOWS 1 +#define CUTE_FILES_MAC 2 +#define CUTE_FILES_UNIX 3 + +#if defined(_WIN32) + #define CUTE_FILES_PLATFORM CUTE_FILES_WINDOWS + #if !defined(_CRT_SECURE_NO_WARNINGS) + #define _CRT_SECURE_NO_WARNINGS + #endif +#elif defined(__APPLE__) + #define CUTE_FILES_PLATFORM CUTE_FILES_MAC +#else + #define CUTE_FILES_PLATFORM CUTE_FILES_UNIX +#endif + +#include // strerror, strncpy + +// change to 0 to compile out any debug checks +#define CUTE_FILES_DEBUG_CHECKS 1 + +#if CUTE_FILES_DEBUG_CHECKS + + #include // printf + #include // assert + #include + #define CUTE_FILES_ASSERT assert + +#else + + #define CUTE_FILES_ASSERT(...) + +#endif // CUTE_FILES_DEBUG_CHECKS + +#define CUTE_FILES_MAX_PATH 1024 +#define CUTE_FILES_MAX_FILENAME 256 +#define CUTE_FILES_MAX_EXT 32 + +struct cf_file_t; +struct cf_dir_t; +struct cf_time_t; +typedef struct cf_file_t cf_file_t; +typedef struct cf_dir_t cf_dir_t; +typedef struct cf_time_t cf_time_t; +typedef void (cf_callback_t)(cf_file_t* file, void* udata); + +// Stores the file extension in cf_file_t::ext, and returns a pointer to +// cf_file_t::ext +const char* cf_get_ext(cf_file_t* file); + +// Applies a function (cb) to all files in a directory. Will recursively visit +// all subdirectories. Useful for asset management, file searching, indexing, etc. +void cf_traverse(const char* path, cf_callback_t* cb, void* udata); + +// Fills out a cf_file_t struct with file information. Does not actually open the +// file contents, and instead performs more lightweight OS-specific calls. +int cf_read_file(cf_dir_t* dir, cf_file_t* file); + +// Once a cf_dir_t is opened, this function can be used to grab another file +// from the operating system. +void cf_dir_next(cf_dir_t* dir); + +// Performs lightweight OS-specific call to close internal handle. +void cf_dir_close(cf_dir_t* dir); + +// Performs lightweight OS-specific call to open a file handle on a directory. +int cf_dir_open(cf_dir_t* dir, const char* path); + +// Compares file last write times. -1 if file at path_a was modified earlier than path_b. +// 0 if they are equal. 1 if file at path_b was modified earlier than path_a. +int cf_compare_file_times_by_path(const char* path_a, const char* path_b); + +// Retrieves time file was last modified, returns 0 upon failure +int cf_get_file_time(const char* path, cf_time_t* time); + +// Compares file last write times. -1 if time_a was modified earlier than path_b. +// 0 if they are equal. 1 if time_b was modified earlier than path_a. +int cf_compare_file_times(cf_time_t* time_a, cf_time_t* time_b); + +// Returns 1 of file exists, otherwise returns 0. +int cf_file_exists(const char* path); + +// Returns 1 if the file's extension matches the string in ext +// Returns 0 otherwise +int cf_match_ext(cf_file_t* file, const char* ext); + +// Prints detected errors to stdout +void cf_do_unit_tests(); + +#if CUTE_FILES_PLATFORM == CUTE_FILES_WINDOWS + +#if !defined _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif +#include + + struct cf_file_t + { + char path[CUTE_FILES_MAX_PATH]; + char name[CUTE_FILES_MAX_FILENAME]; + char ext[CUTE_FILES_MAX_EXT]; + int is_dir; + int is_reg; + size_t size; + }; + + struct cf_dir_t + { + char path[CUTE_FILES_MAX_PATH]; + int has_next; + HANDLE handle; + WIN32_FIND_DATAA fdata; + }; + + struct cf_time_t + { + FILETIME time; + }; + +#elif CUTE_FILES_PLATFORM == CUTE_FILES_MAC || CUTE_FILES_PLATFORM == CUTE_FILES_UNIX + + #include + #include + #include + #include + + struct cf_file_t + { + char path[CUTE_FILES_MAX_PATH]; + char name[CUTE_FILES_MAX_FILENAME]; + char ext[CUTE_FILES_MAX_EXT]; + int is_dir; + int is_reg; + int size; + struct stat info; + }; + + struct cf_dir_t + { + char path[CUTE_FILES_MAX_PATH]; + int has_next; + DIR* dir; + struct dirent* entry; + }; + + struct cf_time_t + { + time_t time; + }; + +#endif + +#define CUTE_FILES_H +#endif + +#ifdef CUTE_FILES_IMPLEMENTATION +#ifndef CUTE_FILES_IMPLEMENTATION_ONCE +#define CUTE_FILES_IMPLEMENTATION_ONCE + +#define cf_safe_strcpy(dst, src, n, max) cf_safe_strcpy_internal(dst, src, n, max, __FILE__, __LINE__) +static int cf_safe_strcpy_internal(char* dst, const char* src, int n, int max, const char* file, int line) +{ + int c; + const char* original = src; + + do + { + if (n >= max) + { + if (!CUTE_FILES_DEBUG_CHECKS) break; + printf("ERROR: String \"%s\" too long to copy on line %d in file %s (max length of %d).\n" + , original + , line + , file + , max); + CUTE_FILES_ASSERT(0); + } + + c = *src++; + dst[n] = c; + ++n; + } while (c); + + return n; +} + +const char* cf_get_ext(cf_file_t* file) +{ + char* name = file->name; + char* period = NULL; + while (*name++) if (*name == '.') period = name; + if (period) cf_safe_strcpy(file->ext, period, 0, CUTE_FILES_MAX_EXT); + else file->ext[0] = 0; + return file->ext; +} + +void cf_traverse(const char* path, cf_callback_t* cb, void* udata) +{ + cf_dir_t dir; + cf_dir_open(&dir, path); + + while (dir.has_next) + { + cf_file_t file; + int res = cf_read_file(&dir, &file); + + if (res == 0) { + cf_dir_next(&dir); + continue; + } + + if (file.is_dir && file.name[0] != '.') + { + char path2[CUTE_FILES_MAX_PATH]; + int n = cf_safe_strcpy(path2, path, 0, CUTE_FILES_MAX_PATH); + n = cf_safe_strcpy(path2, "/", n - 1, CUTE_FILES_MAX_PATH); + cf_safe_strcpy(path2, file.name, n -1, CUTE_FILES_MAX_PATH); + cf_traverse(path2, cb, udata); + } + + if (file.is_reg) cb(&file, udata); + cf_dir_next(&dir); + } + + cf_dir_close(&dir); +} + +int cf_match_ext(cf_file_t* file, const char* ext) +{ + return !strcmp(file->ext, ext); +} + +#if CUTE_FILES_PLATFORM == CUTE_FILES_WINDOWS + + int cf_read_file(cf_dir_t* dir, cf_file_t* file) + { + CUTE_FILES_ASSERT(dir->handle != INVALID_HANDLE_VALUE); + + int n = 0; + char* fpath = file->path; + char* dpath = dir->path; + + n = cf_safe_strcpy(fpath, dpath, 0, CUTE_FILES_MAX_PATH); + n = cf_safe_strcpy(fpath, "/", n - 1, CUTE_FILES_MAX_PATH); + + char* dname = dir->fdata.cFileName; + char* fname = file->name; + + cf_safe_strcpy(fname, dname, 0, CUTE_FILES_MAX_FILENAME); + cf_safe_strcpy(fpath, fname, n - 1, CUTE_FILES_MAX_PATH); + + size_t max_dword = MAXDWORD; + file->size = ((size_t)dir->fdata.nFileSizeHigh * (max_dword + 1)) + (size_t)dir->fdata.nFileSizeLow; + cf_get_ext(file); + + file->is_dir = !!(dir->fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + file->is_reg = !!(dir->fdata.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) || + !(dir->fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + + return 1; + } + + void cf_dir_next(cf_dir_t* dir) + { + CUTE_FILES_ASSERT(dir->has_next); + + if (!FindNextFileA(dir->handle, &dir->fdata)) + { + dir->has_next = 0; + DWORD err = GetLastError(); + CUTE_FILES_ASSERT(err == ERROR_SUCCESS || err == ERROR_NO_MORE_FILES); + } + } + + void cf_dir_close(cf_dir_t* dir) + { + dir->path[0] = 0; + dir->has_next = 0; + if (dir->handle != INVALID_HANDLE_VALUE) FindClose(dir->handle); + } + + int cf_dir_open(cf_dir_t* dir, const char* path) + { + int n = cf_safe_strcpy(dir->path, path, 0, CUTE_FILES_MAX_PATH); + n = cf_safe_strcpy(dir->path, "\\*", n - 1, CUTE_FILES_MAX_PATH); + dir->handle = FindFirstFileA(dir->path, &dir->fdata); + dir->path[n - 3] = 0; + + if (dir->handle == INVALID_HANDLE_VALUE) + { + printf("ERROR: Failed to open directory (%s): %s.\n", path, strerror(errno)); + cf_dir_close(dir); + CUTE_FILES_ASSERT(0); + return 0; + } + + dir->has_next = 1; + + return 1; + } + + int cf_compare_file_times_by_path(const char* path_a, const char* path_b) + { + FILETIME time_a = { 0 }; + FILETIME time_b = { 0 }; + WIN32_FILE_ATTRIBUTE_DATA data; + + if (GetFileAttributesExA(path_a, GetFileExInfoStandard, &data)) time_a = data.ftLastWriteTime; + if (GetFileAttributesExA(path_b, GetFileExInfoStandard, &data)) time_b = data.ftLastWriteTime; + return CompareFileTime(&time_a, &time_b); + } + + int cf_get_file_time(const char* path, cf_time_t* time) + { + FILETIME initialized_to_zero = { 0 }; + time->time = initialized_to_zero; + WIN32_FILE_ATTRIBUTE_DATA data; + if (GetFileAttributesExA(path, GetFileExInfoStandard, &data)) + { + time->time = data.ftLastWriteTime; + return 1; + } + return 0; + } + + int cf_compare_file_times(cf_time_t* time_a, cf_time_t* time_b) + { + return CompareFileTime(&time_a->time, &time_b->time); + } + + int cf_file_exists(const char* path) + { + WIN32_FILE_ATTRIBUTE_DATA unused; + return GetFileAttributesExA(path, GetFileExInfoStandard, &unused); + } + +#elif CUTE_FILES_PLATFORM == CUTE_FILES_MAC || CUTE_FILES_PLATFORM == CUTE_FILES_UNIX + + int cf_read_file(cf_dir_t* dir, cf_file_t* file) + { + CUTE_FILES_ASSERT(dir->entry); + + int n = 0; + char* fpath = file->path; + char* dpath = dir->path; + + n = cf_safe_strcpy(fpath, dpath, 0, CUTE_FILES_MAX_PATH); + n = cf_safe_strcpy(fpath, "/", n - 1, CUTE_FILES_MAX_PATH); + + char* dname = dir->entry->d_name; + char* fname = file->name; + + cf_safe_strcpy(fname, dname, 0, CUTE_FILES_MAX_FILENAME); + cf_safe_strcpy(fpath, fname, n - 1, CUTE_FILES_MAX_PATH); + + if (stat(file->path, &file->info)) + return 0; + + file->size = file->info.st_size; + cf_get_ext(file); + + file->is_dir = S_ISDIR(file->info.st_mode); + file->is_reg = S_ISREG(file->info.st_mode); + + return 1; + } + + void cf_dir_next(cf_dir_t* dir) + { + CUTE_FILES_ASSERT(dir->has_next); + dir->entry = readdir(dir->dir); + dir->has_next = dir->entry ? 1 : 0; + } + + void cf_dir_close(cf_dir_t* dir) + { + dir->path[0] = 0; + if (dir->dir) closedir(dir->dir); + dir->dir = 0; + dir->has_next = 0; + dir->entry = 0; + } + + int cf_dir_open(cf_dir_t* dir, const char* path) + { + cf_safe_strcpy(dir->path, path, 0, CUTE_FILES_MAX_PATH); + dir->dir = opendir(path); + + if (!dir->dir) + { + printf("ERROR: Failed to open directory (%s): %s.\n", path, strerror(errno)); + cf_dir_close(dir); + CUTE_FILES_ASSERT(0); + return 0; + } + + dir->has_next = 1; + dir->entry = readdir(dir->dir); + if (!dir->dir) dir->has_next = 0; + + return 1; + } + + // Warning : untested code! (let me know if it breaks) + int cf_compare_file_times_by_path(const char* path_a, const char* path_b) + { + time_t time_a; + time_t time_b; + struct stat info; + if (stat(path_a, &info)) return 0; + time_a = info.st_mtime; + if (stat(path_b, &info)) return 0; + time_b = info.st_mtime; + return (int)difftime(time_a, time_b); + } + + // Warning : untested code! (let me know if it breaks) + int cf_get_file_time(const char* path, cf_time_t* time) + { + struct stat info; + if (stat(path, &info)) return 0; + time->time = info.st_mtime; + return 1; + } + + // Warning : untested code! (let me know if it breaks) + int cf_compare_file_times(cf_time_t* time_a, cf_time_t* time_b) + { + return (int)difftime(time_a->time, time_b->time); + } + + // Warning : untested code! (let me know if it breaks) + int cf_file_exists(const char* path) + { + return access(path, F_OK) != -1; + } + +#endif // CUTE_FILES_PLATFORM + +#endif // CUTE_FILES_IMPLEMENTATION_ONCE +#endif // CUTE_FILES_IMPLEMENTATION + +/* + ------------------------------------------------------------------------------ + This software is available under 2 licenses - you may choose the one you like. + ------------------------------------------------------------------------------ + ALTERNATIVE A - zlib license + Copyright (c) 2017 Randy Gaul http://www.randygaul.net + 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. + ------------------------------------------------------------------------------ + ALTERNATIVE B - Public Domain (www.unlicense.org) + This is free and unencumbered software released into the public domain. + Anyone is free to copy, modify, publish, use, compile, sell, or distribute this + software, either in source code form or as a compiled binary, for any purpose, + commercial or non-commercial, and by any means. + In jurisdictions that recognize copyright laws, the author or authors of this + software dedicate any and all copyright interest in the software to the public + domain. We make this dedication for the benefit of the public at large and to + the detriment of our heirs and successors. We intend this dedication to be an + overt act of relinquishment in perpetuity of all present and future rights to + this software under copyright law. + 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 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. + ------------------------------------------------------------------------------ +*/