Files
cell/source/qjs_io.c
2025-11-18 19:12:30 -06:00

430 lines
11 KiB
C

#include "qjs_io.h"
#include "jsffi.h"
#include "qjs_macros.h"
#include "cell.h"
#include "qjs_blob.h"
#include <physfs.h>
#include <string.h>
#include <stdlib.h>
#include "wildmatch.h"
// Helper function for array length using QuickJS
// JS_ArrayLength removed - use JS_ArrayLength directly
// PHYSFS_File free function
static void PHYSFS_File_free(JSRuntime *rt, PHYSFS_File *f)
{
PHYSFS_close(f);
}
// Class definition for PHYSFS_File
QJSCLASS(PHYSFS_File,)
// Helper function for writing to PHYSFS
static size_t js_physfs_write(JSContext *js, PHYSFS_File *f, JSValue val)
{
size_t len;
size_t wrote;
if (JS_IsString(val)) {
const char *data = JS_ToCStringLen(js,&len,val);
wrote = PHYSFS_writeBytes(f,data,len);
JS_FreeCString(js,data);
} else {
unsigned char *data = js_get_blob_data(js,&len,val);
wrote = PHYSFS_writeBytes(f,data,len);
}
if (wrote < len) wrote = -1;
return wrote;
}
// Glob data structure for filesystem operations
struct globdata {
JSContext *js;
JSValue arr;
char **globs;
char *glob;
int idx;
int recurse;
};
// Callback for enumerate
static int enumerate_cb(void *udata, const char *dir, const char *fname)
{
struct globdata *data = (struct globdata*)udata;
char *path;
int needfree = 0;
if (dir[0] == 0) path = (char*)fname;
else {
size_t dlen = strlen(dir);
int ends_slash = (dlen && dir[dlen - 1] == '/');
path = malloc(dlen + strlen(fname) + (ends_slash ? 1 : 2));
if (ends_slash) sprintf(path, "%s%s", dir, fname); else sprintf(path, "%s/%s", dir, fname);
needfree = 1;
}
PHYSFS_Stat st;
if (!PHYSFS_stat(path, &st)) {
if (needfree) free(path);
return 1;
}
/* If it's a directory and we're recursing, enumerate it further. */
if (st.filetype == PHYSFS_FILETYPE_DIRECTORY && data->recurse)
PHYSFS_enumerate(path, enumerate_cb, data);
/* Add this item (file or directory) to the JS array. */
JS_SetPropertyUint32(data->js, data->arr, data->idx++, JS_NewString(data->js, path));
if (needfree) free(path);
return 1; /* continue enumerating */
}
// I/O FUNCTIONS
JSC_SCALL(io_rm,
if (!PHYSFS_delete(str)) ret = JS_ThrowReferenceError(js,"could not remove %s. %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_SCALL(io_mkdir,
if (!PHYSFS_mkdir(str)) ret = JS_ThrowReferenceError(js,"could not make directory %s. %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_SCALL(io_exists, ret = JS_NewBool(js,PHYSFS_exists(str)); )
JSC_SCALL(io_stat,
PHYSFS_Stat stat;
if (!PHYSFS_stat(str, &stat))
return JS_ThrowReferenceError(js, "%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
ret = JS_NewObject(js);
JS_SetPropertyStr(js,ret,"filesize", number2js(js,stat.filesize));
JS_SetPropertyStr(js,ret,"modtime", number2js(js,stat.modtime));
JS_SetPropertyStr(js,ret,"createtime", number2js(js,stat.createtime));
JS_SetPropertyStr(js,ret,"accesstime", number2js(js,stat.accesstime));
)
JSC_SCALL(io_slurpbytes,
PHYSFS_File *f = PHYSFS_openRead(str);
if (!f) {
ret = JS_ThrowReferenceError(js,"physfs error when slurping %s: %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
goto END;
}
PHYSFS_Stat stat;
PHYSFS_stat(str,&stat);
void *data = malloc(stat.filesize);
PHYSFS_readBytes(f,data,stat.filesize);
PHYSFS_close(f);
ret = js_new_blob_stoned_copy(js,data,stat.filesize);
END:
)
JSC_SCALL(io_slurp,
PHYSFS_File *f = PHYSFS_openRead(str);
if (!f) {
ret = JS_ThrowReferenceError(js,"physfs error when slurping %s: %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
goto END;
}
PHYSFS_Stat stat;
PHYSFS_stat(str,&stat);
void *data = malloc(stat.filesize);
PHYSFS_readBytes(f,data,stat.filesize);
PHYSFS_close(f);
ret = JS_NewStringLen(js,data, stat.filesize);
free(data);
END:
)
JSC_SCALL(io_slurpwrite,
PHYSFS_File *f = PHYSFS_openWrite(str);
if (!f) {
ret = JS_ThrowReferenceError(js,"could not write to %s: %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
goto END;
}
size_t wrote = js_physfs_write(js,f,argv[1]);
PHYSFS_close(f);
if (wrote == -1)
ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
END:
)
JSC_CCALL(io_mount,
const char *src = JS_ToCString(js, argv[0]);
const char *mountpoint;
if (JS_IsNull(argv[1]))
mountpoint = NULL;
else
mountpoint = JS_ToCString(js, argv[1]);
int prepend = 0;
if (argc > 2 && !JS_IsNull(argv[2]))
prepend = JS_ToBool(js, argv[2]);
if (!PHYSFS_mount(src, mountpoint, prepend))
ret = JS_ThrowReferenceError(js,"Unable to mount %s at %s: %s", src, mountpoint, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
JS_FreeCString(js, src);
if (mountpoint)
JS_FreeCString(js, mountpoint);
)
JSC_SCALL(io_unmount,
if (!PHYSFS_unmount(str)) ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_SCALL(io_writepath,
if (!PHYSFS_setWriteDir(str)) ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_SSCALL(io_match,
if (wildmatch(str, str2, WM_PATHNAME | WM_PERIOD | WM_WILDSTAR) == WM_MATCH)
ret = JS_NewBool(js,1);
else
ret = JS_NewBool(js,0);
)
// Callback for globfs, with support for negative patterns (e.g. "!**/.git")
static int globfs_cb(struct globdata *data, char *dir, char *file)
{
int needfree = 0;
char *path;
PHYSFS_Stat stat;
char **glob;
if (dir[0] == 0)
path = file;
else {
path = malloc(strlen(dir) + strlen(file) + 2);
path[0] = 0;
strcat(path, dir);
strcat(path, "/");
strcat(path, file);
needfree = 1;
}
// Grab filetype now
PHYSFS_stat(path, &stat);
if (stat.filetype == PHYSFS_FILETYPE_DIRECTORY) {
// Check negative patterns first: if directory matches a "!…" pattern, skip it
for (glob = data->globs; *glob != NULL; glob++) {
if ((*glob)[0] == '!') {
const char *neg_pattern = (*glob) + 1;
if (wildmatch(neg_pattern, path, WM_WILDSTAR) == WM_MATCH)
goto END;
}
}
// Not excluded: recurse into this directory
PHYSFS_enumerate(path, globfs_cb, data);
goto END;
}
// It's a file—first see if it matches any negative pattern
for (glob = data->globs; *glob != NULL; glob++) {
if ((*glob)[0] == '!') {
const char *neg_pattern = (*glob) + 1;
if (wildmatch(neg_pattern, path, WM_WILDSTAR) == WM_MATCH)
goto END;
}
}
// Now check positive patterns ("**/*.cm", "**/*.ce", etc.)
for (glob = data->globs; *glob != NULL; glob++) {
if ((*glob)[0] == '!')
continue;
if (wildmatch(*glob, path, WM_WILDSTAR) == WM_MATCH) {
JS_SetPropertyUint32(data->js,
data->arr,
data->idx++,
JS_NewString(data->js, path));
break;
}
}
END:
if (needfree)
free(path);
return 1;
}
// arg0: glob patterns (array of strings)
// arg1: directory (string)
JSC_CCALL(io_globfs,
ret = JS_NewArray(js);
struct globdata data;
data.js = js;
data.arr = ret;
data.idx = 0;
int globs_len = JS_ArrayLength(js,argv[0]);
const char *globs[globs_len+1];
for (int i = 0; i < globs_len; i++) {
JSValue g = JS_GetPropertyUint32(js,argv[0],i);
globs[i] = JS_ToCString(js,g);
JS_FreeValue(js,g);
}
globs[globs_len] = NULL;
data.globs = globs;
const char *path = NULL;
if (!JS_IsNull(argv[1])) path = JS_ToCString(js,argv[1]);
PHYSFS_enumerate(path, globfs_cb, &data);
for (int i = 0; i < globs_len; i++)
JS_FreeCString(js,globs[i]);
ret = data.arr;
JS_FreeCString(js,path);
)
JSC_SCALL(io_enumerate,
/* First argument: str (directory name) */
/* Second argument: boolean => recurse or not */
ret = JS_NewArray(js);
struct globdata data;
data.js = js;
data.arr = ret;
data.idx = 0;
data.glob = NULL; /* not used here */
data.globs = NULL; /* not used here */
data.recurse = 0;
if (!JS_IsNull(argv[1])) /* parse second arg if provided */
data.recurse = JS_ToBool(js, argv[1]);
/* Enumerate the directory given by 'str'. */
PHYSFS_enumerate(str, enumerate_cb, &data);
/* Return the JS array we filled. */
ret = data.arr;
)
JSC_CCALL(io_basedir, return JS_NewString(js,PHYSFS_getBaseDir()))
JSC_SSCALL(io_prefdir, return JS_NewString(js,PHYSFS_getPrefDir(str, str2)))
JSC_SCALL(io_open,
PHYSFS_File *f = PHYSFS_openWrite(str);
if (!f)
ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
else
ret = PHYSFS_File2js(js,f);
)
JSC_SCALL(io_realdir,
const char *real = PHYSFS_getRealDir(str);
if (!real)
ret = JS_NULL;
else
ret = JS_NewString(js,real);
)
JSC_CCALL(io_searchpath,
ret = JS_NewArray(js);
char **paths = PHYSFS_getSearchPath();
for (int i = 0; paths[i] != NULL; i++)
JS_SetPropertyUint32(js,ret,i,JS_NewString(js,paths[i]));
PHYSFS_freeList(paths);
)
JSC_SCALL(io_is_directory,
PHYSFS_Stat stat;
int good = PHYSFS_stat(str, &stat);
if (!good)
ret = JS_NewBool(js, 0);
else
ret = JS_NewBool(js, stat.filetype == PHYSFS_FILETYPE_DIRECTORY);
)
// FILE FUNCTIONS
JSC_CCALL(file_close,
PHYSFS_File *f = js2PHYSFS_File(js,self);
PHYSFS_close(f);
)
JSC_CCALL(file_write,
PHYSFS_File *f = js2PHYSFS_File(js,self);
size_t wrote = js_physfs_write(js,f,argv[0]);
if (wrote == -1)
return JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_CCALL(file_buffer,
PHYSFS_File *f = js2PHYSFS_File(js,self);
size_t size = js2number(js,argv[0]);
if (!PHYSFS_setBuffer(f,size))
return JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_CCALL(file_tell,
PHYSFS_File *f = js2PHYSFS_File(js,self);
size_t tell = PHYSFS_tell(f);
if (tell == -1)
return JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
return number2js(js,tell);
)
JSC_CCALL(file_eof,
PHYSFS_File *f = js2PHYSFS_File(js,self);
return JS_NewBool(js, PHYSFS_eof(f));
)
JSC_CCALL(io_mountpoint,
const char *path = JS_ToCString(js,argv[0]);
const char *point = PHYSFS_getMountPoint(path);
if (point)
return JS_NewString(js,point);
)
static const JSCFunctionListEntry js_io_funcs[] = {
MIST_FUNC_DEF(io, rm, 1),
MIST_FUNC_DEF(io, mkdir, 1),
MIST_FUNC_DEF(io,stat,1),
MIST_FUNC_DEF(io, globfs, 2),
MIST_FUNC_DEF(io, match, 2),
MIST_FUNC_DEF(io, exists, 1),
MIST_FUNC_DEF(io, mount, 3),
MIST_FUNC_DEF(io,unmount,1),
MIST_FUNC_DEF(io,slurp,1),
MIST_FUNC_DEF(io,slurpbytes,1),
MIST_FUNC_DEF(io,slurpwrite,2),
MIST_FUNC_DEF(io,writepath, 1),
MIST_FUNC_DEF(io,basedir, 0),
MIST_FUNC_DEF(io, prefdir, 2),
MIST_FUNC_DEF(io, realdir, 1),
MIST_FUNC_DEF(io, open, 1),
MIST_FUNC_DEF(io, searchpath, 0),
MIST_FUNC_DEF(io, enumerate, 2),
MIST_FUNC_DEF(io, is_directory, 1),
MIST_FUNC_DEF(io, mountpoint, 1),
};
static const JSCFunctionListEntry js_PHYSFS_File_funcs[] = {
MIST_FUNC_DEF(file, close, 0),
MIST_FUNC_DEF(file, write, 1),
MIST_FUNC_DEF(file, buffer, 1),
MIST_FUNC_DEF(file, tell, 0),
MIST_FUNC_DEF(file, eof, 0),
};
JSValue js_io_use(JSContext *js) {
QJSCLASSPREP_FUNCS(PHYSFS_File);
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_io_funcs,countof(js_io_funcs));
return mod;
}