Преглед изворни кода

Added optional File class (to be stress tested, expecially on Windows)

Marco Bambini пре 5 година
родитељ
комит
ce5c8745a1

+ 1 - 1
src/cli/gravity.c

@@ -173,7 +173,7 @@ static void unittest_scan (const char *folder_path, unittest_data *data) {
     #ifdef WIN32
     char outbuffer[MAX_PATH];
     #else
-    char * outbuffer = NULL;
+    char *outbuffer = NULL;
     #endif
     const char *target_file;
     while ((target_file = directory_read(dir, outbuffer))) {

+ 299 - 0
src/optionals/gravity_opt_file.c

@@ -0,0 +1,299 @@
+//
+//  gravity_opt_file.c
+//  gravity
+//
+//  Created by Marco Bambini on 15/11/2020.
+//  Copyright © 2020 Creolabs. All rights reserved.
+//
+
+#include "gravity_vm.h"
+#include "gravity_core.h"
+#include "gravity_hash.h"
+#include "gravity_utils.h"
+#include "gravity_macros.h"
+#include "gravity_vmmacros.h"
+#include "gravity_opt_file.h"
+
+static gravity_class_t              *gravity_class_file = NULL;
+static uint32_t                     refcount = 0;
+
+// MARK: - Implementation -
+
+/*
+     GRAVITY EXAMPLE
+     ==============
+    
+     func main() {
+         var target_file = "FULL_PATH_TO_A_TEXT_FILE_HERE";
+         var target_folder = "FULL_PATH_TO_A_FOLDER_HERE";
+ 
+         // FILE TEST
+         var size = File.size(target_file);
+         var exists = File.exists(target_file);
+         var is_dir = File.is_directory(target_file);
+         var data = File.read(target_file);
+         
+         System.print("File: " + target_file);
+         System.print("Size: " + size);
+         System.print("Exists: " + exists);
+         System.print("Is Directory: " + is_dir);
+         System.print("Data: " + data);
+         
+         // FOLDER TEST
+         func closure (file_name, full_path, is_directory) {
+             if (is_directory) {
+                 System.print("+ \(file_name)");
+             } else {
+                 System.print("    \(file_name)");
+             }
+         }
+         
+         var recursive = true;
+         var n = File.directory_scan(target_folder, recursive, closure);
+         
+         // return the number of file processed
+         return n;
+     }
+ 
+ */
+
+static bool internal_file_size (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+    // 1 parameter of type string is required
+    if (nargs != 2 && !VALUE_ISA_STRING(args[1])) {
+        RETURN_ERROR("A path parameter of type String is required.");
+    }
+    
+    char *path = VALUE_AS_STRING(args[1])->s;
+    int64_t size = file_size((const char *)path);
+    RETURN_VALUE(VALUE_FROM_INT(size), rindex);
+}
+
+static bool internal_file_exists (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+    // 1 parameter of type string is required
+    if (nargs != 2 && !VALUE_ISA_STRING(args[1])) {
+        RETURN_ERROR("A path parameter of type String is required.");
+    }
+    
+    char *path = VALUE_AS_STRING(args[1])->s;
+    bool exists = file_exists((const char *)path);
+    RETURN_VALUE(VALUE_FROM_BOOL(exists), rindex);
+}
+
+static bool internal_file_delete (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+    // 1 parameter of type string is required
+    if (nargs != 2 && !VALUE_ISA_STRING(args[1])) {
+        RETURN_ERROR("A path parameter of type String is required.");
+    }
+    
+    char *path = VALUE_AS_STRING(args[1])->s;
+    bool result = file_delete((const char *)path);
+    RETURN_VALUE(VALUE_FROM_BOOL(result), rindex);
+}
+
+static bool internal_file_read (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+    // 1 parameter of type string is required
+    if (nargs != 2 && !VALUE_ISA_STRING(args[1])) {
+        RETURN_ERROR("A path parameter of type String is required.");
+    }
+    
+    char *path = VALUE_AS_STRING(args[1])->s;
+    size_t len = 0;
+    char *buffer = file_read((const char *)path, &len);
+    if (!buffer) {
+        RETURN_VALUE(VALUE_FROM_NULL, rindex);
+    }
+    
+    gravity_value_t string = VALUE_FROM_STRING(vm, buffer, (uint32_t)len);
+    RETURN_VALUE(string, rindex);
+}
+
+static bool internal_file_write (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+    // 2 parameters of type string are required
+    if (nargs != 3 && !VALUE_ISA_STRING(args[1]) && !VALUE_ISA_STRING(args[2])) {
+        RETURN_ERROR("A path parameter of type String and a String parameter are required.");
+    }
+    
+    char *path = VALUE_AS_STRING(args[1])->s;
+    char *buffer = VALUE_AS_STRING(args[2])->s;
+    size_t len = (size_t)VALUE_AS_STRING(args[2])->len;
+    bool result = file_write((const char *)path, buffer, len);
+    RETURN_VALUE(VALUE_FROM_BOOL(result), rindex);
+}
+
+static bool internal_file_buildpath (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+    // 2 parameters of type string are required
+    if (nargs != 3 && !VALUE_ISA_STRING(args[1]) && !VALUE_ISA_STRING(args[2])) {
+        RETURN_ERROR("A file and path parameters of type String are required.");
+    }
+    
+    char *file = VALUE_AS_STRING(args[1])->s;
+    char *path = VALUE_AS_STRING(args[2])->s;
+    char *result = file_buildpath((const char *)file, (const char *)path);
+    
+    if (!result) {
+        RETURN_VALUE(VALUE_FROM_NULL, rindex);
+    }
+    
+    gravity_value_t string = VALUE_FROM_STRING(vm, result, (uint32_t)strlen(result));
+    RETURN_VALUE(string, rindex);
+}
+
+static bool internal_file_is_directory (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+    // 1 parameter of type string is required
+    if (nargs != 2 && !VALUE_ISA_STRING(args[1])) {
+        RETURN_ERROR("A path parameter of type String is required.");
+    }
+    
+    char *path = VALUE_AS_STRING(args[1])->s;
+    bool result = is_directory((const char *)path);
+    RETURN_VALUE(VALUE_FROM_BOOL(result), rindex);
+}
+
+static bool internal_file_directory_create (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+    // 1 parameter of type string is required
+    if (nargs != 2 && !VALUE_ISA_STRING(args[1])) {
+        RETURN_ERROR("A path parameter of type String is required.");
+    }
+    
+    char *path = VALUE_AS_STRING(args[1])->s;
+    bool result = directory_create((const char *)path);
+    RETURN_VALUE(VALUE_FROM_BOOL(result), rindex);
+}
+
+static void scan_directory (gravity_vm *vm, char *path, bool recursive, gravity_closure_t *closure, gravity_int_t *n, bool isdir) {
+    DIRREF dir = directory_init ((const char *)path);
+    if (!dir) return;
+    
+    if (isdir) {
+        // report directory name
+        char *name = file_name_frompath(path);
+        gravity_value_t p1 = VALUE_FROM_CSTRING(vm, name);
+        mem_free(name);
+        gravity_value_t p2 = VALUE_FROM_CSTRING(vm, path);
+        gravity_value_t p3 = VALUE_FROM_BOOL(true);
+        gravity_value_t params[] = {p1, p2, p3};
+        
+        gravity_vm_runclosure(vm, closure, VALUE_FROM_NULL, params, 3);
+        if (n) *n = *n + 1;
+    }
+    
+    #ifdef WIN32
+    char buffer[MAX_PATH];
+    #else
+    char *buffer = NULL;
+    #endif
+
+    const char *target_file;
+    while ((target_file = directory_read_extend(dir, buffer))) {
+        char *full_path = file_buildpath(target_file, path);
+        if (recursive && (is_directory(full_path))) {
+            scan_directory(vm, full_path, recursive, closure, n, true);
+            continue;
+        }
+        
+        // call user closure with target_file and full_path
+        gravity_value_t p1 = VALUE_FROM_CSTRING(vm, target_file);
+        gravity_value_t p2 = VALUE_FROM_CSTRING(vm, full_path);
+        gravity_value_t p3 = VALUE_FROM_BOOL(false);
+        gravity_value_t params[] = {p1, p2, p3};
+        mem_free(full_path);
+        
+        gravity_vm_runclosure(vm, closure, VALUE_FROM_NULL, params, 3);
+        if (n) *n = *n + 1;
+    }
+}
+
+static bool internal_file_directory_scan (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+    char *path = NULL;
+    
+    // check for minimum params
+    if (nargs < 3) {
+        RETURN_ERROR("A path and a closure parameter are required.");
+    }
+    
+    // first parameter of type string is required
+    if (!VALUE_ISA_STRING(args[1])) {
+        RETURN_ERROR("A path parameter of type String is required.");
+    } else {
+        path = VALUE_AS_STRING(args[1])->s;
+    }
+    
+    // optional bool 2nd parameter
+    int nindex = 2;
+    bool recursive = true;
+    if (VALUE_ISA_BOOL(args[2])) {
+        recursive = VALUE_AS_BOOL(args[2]);
+        nindex = 3;
+    }
+    
+    if (!VALUE_ISA_CLOSURE(args[nindex])) {
+        RETURN_ERROR("A closure parameter is required.");
+    }
+    
+    // extract closure
+    gravity_closure_t *closure = VALUE_AS_CLOSURE(args[nindex]);
+    gravity_int_t n = 0;
+    
+    // do not report directory name in the first scan
+    scan_directory(vm, path, recursive, closure, &n, false);
+    
+    /*
+     func closure (var filename, var full_path) {
+        Console.write(filename);
+     }
+     
+     var skipdot = true;
+     var recursive = true;
+     File.directory_scan("/Users/marco/Desktop/", true, recursive, closure);
+     */
+    
+    RETURN_VALUE(VALUE_FROM_INT(n), rindex);
+}
+
+// MARK: - Internals -
+
+static void create_optional_class (void) {
+    gravity_class_file = gravity_class_new_pair(NULL, GRAVITY_CLASS_FILE_NAME, NULL, 0, 0);
+    gravity_class_t *meta = gravity_class_get_meta(gravity_class_file);
+
+    gravity_class_bind(meta, "size", NEW_CLOSURE_VALUE(internal_file_size));
+    gravity_class_bind(meta, "exists", NEW_CLOSURE_VALUE(internal_file_exists));
+    gravity_class_bind(meta, "delete", NEW_CLOSURE_VALUE(internal_file_delete));
+    gravity_class_bind(meta, "read", NEW_CLOSURE_VALUE(internal_file_read));
+    gravity_class_bind(meta, "write", NEW_CLOSURE_VALUE(internal_file_write));
+    gravity_class_bind(meta, "buildpath", NEW_CLOSURE_VALUE(internal_file_buildpath));
+    gravity_class_bind(meta, "is_directory", NEW_CLOSURE_VALUE(internal_file_is_directory));
+    gravity_class_bind(meta, "directory_create", NEW_CLOSURE_VALUE(internal_file_directory_create));
+    gravity_class_bind(meta, "directory_scan", NEW_CLOSURE_VALUE(internal_file_directory_scan));
+
+    SETMETA_INITED(gravity_class_file);
+}
+
+// MARK: - Commons -
+
+bool gravity_isfile_class (gravity_class_t *c) {
+    return (c == gravity_class_file);
+}
+
+const char *gravity_file_name (void) {
+    return GRAVITY_CLASS_FILE_NAME;
+}
+
+void gravity_file_register (gravity_vm *vm) {
+    if (!gravity_class_file) create_optional_class();
+    ++refcount;
+
+    if (!vm || gravity_vm_ismini(vm)) return;
+    gravity_vm_setvalue(vm, GRAVITY_CLASS_FILE_NAME, VALUE_FROM_OBJECT(gravity_class_file));
+}
+
+void gravity_file_free (void) {
+    if (!gravity_class_file) return;
+    if (--refcount) return;
+
+    gravity_class_t *meta = gravity_class_get_meta(gravity_class_file);
+    gravity_class_free_core(NULL, meta);
+    gravity_class_free_core(NULL, gravity_class_file);
+
+    gravity_class_file = NULL;
+}

+ 21 - 0
src/optionals/gravity_opt_file.h

@@ -0,0 +1,21 @@
+//
+//  gravity_opt_file.h
+//  gravity
+//
+//  Created by Marco Bambini on 15/11/2020.
+//  Copyright © 2020 Creolabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_FILE__
+#define __GRAVITY_FILE__
+
+#define GRAVITY_CLASS_FILE_NAME             "File"
+
+#include "gravity_value.h"
+
+void gravity_file_register (gravity_vm *vm);
+void gravity_file_free (void);
+bool gravity_isfile_class (gravity_class_t *c);
+const char *gravity_file_name (void);
+
+#endif

+ 20 - 0
src/optionals/gravity_optionals.h

@@ -60,6 +60,23 @@
 #define GRAVITY_ISENV_CLASS(_c)             false
 #endif
 
+#ifndef GRAVITY_INCLUDE_FILE
+#define GRAVITY_INCLUDE_FILE
+#endif
+
+#ifdef GRAVITY_INCLUDE_FILE
+#define GRAVITY_FILE_REGISTER(_vm)           gravity_file_register(_vm)
+#define GRAVITY_FILE_FREE()                  gravity_file_free()
+#define GRAVITY_FILE_NAME()                  gravity_file_name()
+#define GRAVITY_ISFILE_CLASS(_c)             gravity_isfile_class(_c)
+#include "gravity_opt_file.h"
+#else
+#define GRAVITY_FILE_REGISTER(_vm)
+#define GRAVITY_FILE_FREE()
+#define GRAVITY_FILE_NAME()                  NULL
+#define GRAVITY_ISFILE_CLASS(_c)             false
+#endif
+
 #ifdef _MSC_VER
 #define INLINE								__inline
 #else
@@ -77,6 +94,9 @@ INLINE static const char **gravity_optional_identifiers(void) {
         #ifdef GRAVITY_INCLUDE_JSON
         GRAVITY_CLASS_JSON_NAME,
         #endif
+        #ifdef GRAVITY_INCLUDE_FILE
+        GRAVITY_CLASS_FILE_NAME,
+        #endif
         NULL};
     return list;
 }

+ 2 - 2
src/runtime/gravity_core.c

@@ -1066,7 +1066,7 @@ static bool list_remove (gravity_vm *vm, gravity_value_t *args, uint16_t nargs,
 
 static bool list_iterator (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
     #pragma unused(vm, nargs)
-    gravity_list_t    *list = VALUE_AS_LIST(GET_VALUE(0));
+    gravity_list_t *list = VALUE_AS_LIST(GET_VALUE(0));
 
     // check for empty list first
     register uint32_t count = (uint32_t)marray_size(list->array);
@@ -1095,7 +1095,7 @@ static bool list_iterator (gravity_vm *vm, gravity_value_t *args, uint16_t nargs
 
 static bool list_iterator_next (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
     #pragma unused(vm, nargs)
-    gravity_list_t    *list = VALUE_AS_LIST(GET_VALUE(0));
+    gravity_list_t *list = VALUE_AS_LIST(GET_VALUE(0));
     register int32_t index = (int32_t)VALUE_AS_INT(GET_VALUE(1));
     RETURN_VALUE(marray_get(list->array, index), rindex);
 }

+ 3 - 1
src/runtime/gravity_vm.c

@@ -358,16 +358,18 @@ void gravity_opt_register (gravity_vm *vm) {
     GRAVITY_MATH_REGISTER(vm);
     GRAVITY_ENV_REGISTER(vm);
     GRAVITY_JSON_REGISTER(vm);
+    GRAVITY_FILE_REGISTER(vm);
 }
 
 void gravity_opt_free() {
     GRAVITY_MATH_FREE();
     GRAVITY_ENV_FREE();
     GRAVITY_JSON_FREE();
+    GRAVITY_FILE_FREE();
 }
 
 bool gravity_isopt_class (gravity_class_t *c) {
-    return (GRAVITY_ISMATH_CLASS(c)) || (GRAVITY_ISENV_CLASS(c) || (GRAVITY_ISJSON_CLASS(c)));
+    return (GRAVITY_ISMATH_CLASS(c)) || (GRAVITY_ISENV_CLASS(c) || (GRAVITY_ISJSON_CLASS(c)) || (GRAVITY_ISFILE_CLASS(c)));
 }
 
 // MARK: - MAIN EXECUTION -

+ 2 - 1
src/shared/gravity_config.h

@@ -30,9 +30,10 @@ typedef int         mode_t;
 #define read        _read
 #define write       _write
 #define __func__    __FUNCTION__
-
+#define PATH_SEPARATOR  '\\'
 #else
 #include <unistd.h>
+#define PATH_SEPARATOR  '/'
 #endif
 
 // check if the compiler supports designated initializers when using c++

+ 121 - 63
src/utils/gravity_utils.c

@@ -27,6 +27,7 @@
 #endif
 
 #include "gravity_utils.h"
+#include "gravity_macros.h"
 #include "gravity_memory.h"
 #include "gravity_config.h"
 
@@ -89,23 +90,43 @@ double millitime (nanotime_t tstart, nanotime_t tend) {
 
 // MARK: - I/O Functions -
 
-uint64_t file_size (const char *path) {
+int64_t file_size (const char *path) {
     #ifdef WIN32
     WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
     if (GetFileAttributesExA(path, GetFileExInfoStandard, (void*)&fileInfo) == 0) return -1;
-    return (uint64_t)(((__int64)fileInfo.nFileSizeHigh) << 32 ) + fileInfo.nFileSizeLow;
+    return (int64_t)(((__int64)fileInfo.nFileSizeHigh) << 32 ) + fileInfo.nFileSizeLow;
     #else
     struct stat sb;
     if (stat(path, &sb) < 0) return -1;
-    return (uint64_t)sb.st_size;
+    return (int64_t)sb.st_size;
     #endif
 }
 
-const char *file_read(const char *path, size_t *len) {
-    int		fd = 0;
-    off_t	fsize = 0;
-    size_t	fsize2 = 0;
-    char	*buffer = NULL;
+bool file_exists (const char *path) {
+    #ifdef WIN32
+    if (GetFileAttributesA(path) != INVALID_FILE_ATTRIBUTES) return true;
+    #else
+    if (access(path, F_OK) == 0) return true;
+    #endif
+    
+    return false;
+}
+
+bool file_delete (const char *path) {
+    #ifdef WIN32
+    return DeleteFileA(path);
+    #else
+    if (unlink(path) == 0) return true;
+    #endif
+    
+    return false;
+}
+
+char *file_read(const char *path, size_t *len) {
+    int     fd = 0;
+    off_t   fsize = 0;
+    size_t  fsize2 = 0;
+    char    *buffer = NULL;
     
     fsize = (off_t) file_size(path);
     if (fsize < 0) goto abort_read;
@@ -122,7 +143,7 @@ const char *file_read(const char *path, size_t *len) {
     
     if (len) *len = fsize2;
     close(fd);
-    return (const char *)buffer;
+    return (char *)buffer;
     
 abort_read:
     if (buffer) mem_free((void *)buffer);
@@ -130,74 +151,68 @@ abort_read:
     return NULL;
 }
 
-bool file_exists (const char *path) {
-    #ifdef WIN32
-    BOOL isDirectory;
-    DWORD attributes = GetFileAttributesA(path);
-    
-    // special directory case to drive the network path check
-    if (attributes == INVALID_FILE_ATTRIBUTES)
-        isDirectory = (GetLastError() == ERROR_BAD_NETPATH);
-    else
-        isDirectory = (FILE_ATTRIBUTE_DIRECTORY & attributes);
-    
-    if (isDirectory) {
-        if (PathIsNetworkPathA(path)) return true;
-        if (PathIsUNCA(path)) return true;
-    }
-    
-    if (PathFileExistsA(path) == 1) return true;
+bool file_write (const char *path, const char *buffer, size_t len) {
+    // RW for owner, R for group, R for others
+    #ifdef _WIN32
+    mode_t mode = _S_IWRITE;
     #else
-    if (access(path, F_OK)==0) return true;
+    mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
     #endif
     
-    return false;
+    int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
+    if (fd < 0) return false;
+    
+    ssize_t nwrite = (ssize_t)write(fd, buffer, len);
+    close(fd);
+    
+    return (nwrite == len);
 }
 
-const char *file_buildpath (const char *filename, const char *dirpath) {
-//    #ifdef WIN32
-//    PathCombineA(result, filename, dirpath);
-//    #else
-    size_t len1 = strlen(filename);
-    size_t len2 = strlen(dirpath);
-    size_t len = len1+len2+2;
+char *file_buildpath (const char *filename, const char *dirpath) {
+    size_t len1 = (filename) ? strlen(filename) : 0;
+    size_t len2 = (dirpath) ? strlen(dirpath) : 0;
+    size_t len = len1 + len2 + 4;
     
     char *full_path = (char *)mem_alloc(NULL, len);
     if (!full_path) return NULL;
     
-    if ((len2) && (dirpath[len2-1] != '/'))
+    #ifdef WIN32
+    PathCombineA(full_path, filename, dirpath);
+    #else
+    // check if PATH_SEPARATOR exists in dirpath
+    if ((len2) && (dirpath[len2-1] != PATH_SEPARATOR))
         snprintf(full_path, len, "%s/%s", dirpath, filename);
     else
         snprintf(full_path, len, "%s%s", dirpath, filename);
-//    #endif
+    #endif
     
-    return (const char *)full_path;
+    return full_path;
 }
 
-bool file_write (const char *path, const char *buffer, size_t len) {
-    // RW for owner, R for group, R for others
-    #ifdef _WIN32
-    mode_t mode = _S_IWRITE;
-    #else
-    mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
-    #endif
-    
-    int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
-    if (fd < 0) return false;
+char *file_name_frompath (const char *path) {
+    if (!path || (path[0] == 0)) return NULL;
     
-    ssize_t nwrite = (ssize_t)write(fd, buffer, len);
-    close(fd);
+    // must be sure to have a read-write memory address
+    char *buffer = strdup(path);
+    if (!buffer) return false;
     
-    return (nwrite == len);
+    char *name = NULL;
+    size_t len = strlen(buffer);
+    for (size_t i=len-1; i>0; --i) {
+        if (buffer[i] == PATH_SEPARATOR) {
+            buffer[i] = 0;
+            name = strdup(&buffer[i+1]);
+            break;
+        }
+    }
+    return name;
 }
 
 // MARK: - Directory Functions -
 
 bool is_directory (const char *path) {
     #ifdef WIN32
-    DWORD dwAttrs;
-    
-    dwAttrs = GetFileAttributesA(path);
+    DWORD dwAttrs = GetFileAttributesA(path);
     if (dwAttrs == INVALID_FILE_ATTRIBUTES) return false;
     if (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) return true;
     #else
@@ -210,13 +225,23 @@ bool is_directory (const char *path) {
     return false;
 }
 
+bool directory_create (const char *path) {
+    #ifdef WIN32
+    CreateDirectoryA(path, NULL);
+    #else
+    mode_t saved = umask(0);
+    mkdir(path, 0775);
+    umask(saved);
+    #endif
+    
+    return file_exists(path);
+}
+
 DIRREF directory_init (const char *dirpath) {
 	#ifdef WIN32
 	WIN32_FIND_DATAW findData;
-	WCHAR			path[MAX_PATH];
-	WCHAR			dirpathW[MAX_PATH];
-	HANDLE			hFind;
-	(void)hFind;
+	WCHAR   path[MAX_PATH];
+	WCHAR   dirpathW[MAX_PATH];
 	
 	// convert dirpath to dirpathW
 	MultiByteToWideChar(CP_UTF8, 0, dirpath, -1, dirpathW, MAX_PATH);
@@ -232,8 +257,7 @@ DIRREF directory_init (const char *dirpath) {
 	#endif
 }
 
-const char *directory_read (DIRREF ref, char *out) {
-    #pragma unused (out)
+char *directory_read (DIRREF ref, char *win32buffer) {
 	if (ref == NULL) return NULL;
 	
 	while (1) {
@@ -248,9 +272,10 @@ const char *directory_read (DIRREF ref, char *out) {
 		if (findData.cFileName[0] == '\0') continue;
 		if (findData.cFileName[0] == '.') continue;
 		// cFileName from WIN32_FIND_DATAA is a fixed size array, and findData is local
-		// This line of code is under the assumption that `out` is at least MAX_PATH in size!
-		return !out ? NULL : memcpy(out, findData.cFileName, sizeof(findData.cFileName));
+		// This line of code is under the assumption that `win32buffer` is at least MAX_PATH in size!
+		return !win32buffer ? NULL : memcpy(win32buffer, findData.cFileName, sizeof(findData.cFileName));
 		#else
+        UNUSED_PARAM(win32buffer);
 		struct dirent *d;
 		if ((d = readdir(ref)) == NULL) {
 			closedir(ref);
@@ -258,12 +283,45 @@ const char *directory_read (DIRREF ref, char *out) {
 		}
 		if (d->d_name[0] == '\0') continue;
 		if (d->d_name[0] == '.') continue;
-		return (const char *)d->d_name;
+		return (char *)d->d_name;
 		#endif
 	}
 	return NULL;
 }
 
+char *directory_read_extend (DIRREF ref, char *win32buffer) {
+    if (ref == NULL) return NULL;
+    
+    while (1) {
+        #ifdef WIN32
+        WIN32_FIND_DATAA findData;
+        
+        if (FindNextFileA(ref, &findData) == 0) {
+            FindClose(ref);
+            return NULL;
+        }
+        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;
+        if (findData.cFileName[0] == '\0') continue;
+        if (findData.cFileName[0] == '.') continue;
+        // cFileName from WIN32_FIND_DATAA is a fixed size array, and findData is local
+        // This line of code is under the assumption that `win32buffer` is at least MAX_PATH in size!
+        return !win32buffer ? NULL : memcpy(win32buffer, findData.cFileName, sizeof(findData.cFileName));
+        #else
+        UNUSED_PARAM(win32buffer);
+        struct dirent *d;
+        if ((d = readdir(ref)) == NULL) {
+            closedir(ref);
+            return NULL;
+        }
+        if (d->d_name[0] == '\0') continue;
+        if (strcmp(d->d_name, ".") == 0) continue;
+        if (strcmp(d->d_name, "..") == 0) continue;
+        return (char *)d->d_name;
+        #endif
+    }
+    return NULL;
+}
+
 // MARK: - String Functions -
 
 int string_nocasencmp(const char *s1, const char *s2, size_t n) {

+ 9 - 5
src/utils/gravity_utils.h

@@ -28,17 +28,21 @@ double      microtime (nanotime_t tstart, nanotime_t tend);
 double      millitime (nanotime_t tstart, nanotime_t tend);
 
 // FILE
-uint64_t    file_size (const char *path);
-const char  *file_read (const char *path, size_t *len);
+int64_t     file_size (const char *path);
 bool        file_exists (const char *path);
-const char  *file_buildpath (const char *filename, const char *dirpath);
+bool        file_delete (const char *path);
+char        *file_read (const char *path, size_t *len);
 bool        file_write (const char *path, const char *buffer, size_t len);
+char        *file_buildpath (const char *filename, const char *dirpath);
+char        *file_name_frompath (const char *path);
 
 // DIRECTORY
 bool        is_directory (const char *path);
+bool        directory_create (const char *path);
 DIRREF      directory_init (const char *path);
-// On Windows, you are expected to provied an output buffer of at least MAX_PATH in length
-const char  *directory_read (DIRREF ref, char *out);
+// On Windows, you are expected to provied a win32buffer buffer of at least MAX_PATH in length
+char        *directory_read (DIRREF ref, char *win32buffer);
+char        *directory_read_extend (DIRREF ref, char *win32buffer);
 
 // STRING
 int         string_nocasencmp (const char *s1, const char *s2, size_t n);