turn into a cell package
This commit is contained in:
395
archive/miniz.c
Normal file
395
archive/miniz.c
Normal file
@@ -0,0 +1,395 @@
|
||||
#include "quickjs.h"
|
||||
#include "miniz.h"
|
||||
#include "cell.h"
|
||||
|
||||
static JSClassID js_reader_class_id;
|
||||
static JSClassID js_writer_class_id;
|
||||
|
||||
static void js_reader_finalizer(JSRuntime *rt, JSValue val) {
|
||||
mz_zip_archive *zip = JS_GetOpaque(val, js_reader_class_id);
|
||||
mz_zip_reader_end(zip);
|
||||
js_free_rt(rt,zip);
|
||||
}
|
||||
|
||||
static void js_writer_finalizer(JSRuntime *rt, JSValue val) {
|
||||
mz_zip_archive *zip = JS_GetOpaque(val, js_writer_class_id);
|
||||
mz_zip_writer_finalize_archive(zip);
|
||||
mz_zip_writer_end(zip);
|
||||
js_free_rt(rt,zip);
|
||||
}
|
||||
|
||||
static JSClassDef js_reader_class = {
|
||||
"zip reader",
|
||||
.finalizer = js_reader_finalizer,
|
||||
};
|
||||
|
||||
static JSClassDef js_writer_class = {
|
||||
"zip writer",
|
||||
.finalizer = js_writer_finalizer,
|
||||
};
|
||||
|
||||
static mz_zip_archive *js2reader(JSContext *js, JSValue v)
|
||||
{
|
||||
return JS_GetOpaque(v, js_reader_class_id);
|
||||
}
|
||||
|
||||
static mz_zip_archive *js2writer(JSContext *js, JSValue v)
|
||||
{
|
||||
return JS_GetOpaque(v, js_writer_class_id);
|
||||
}
|
||||
|
||||
static JSValue js_miniz_read(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
size_t len;
|
||||
void *data = js_get_blob_data(js, &len, argv[0]);
|
||||
if (data == -1)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
mz_zip_archive *zip = calloc(sizeof(*zip), 1);
|
||||
if (!zip)
|
||||
return JS_ThrowOutOfMemory(js);
|
||||
|
||||
mz_bool success = mz_zip_reader_init_mem(zip, data, len, 0);
|
||||
|
||||
if (!success) {
|
||||
int err = mz_zip_get_last_error(zip);
|
||||
free(zip);
|
||||
return JS_ThrowInternalError(js, "Failed to initialize zip reader: %s", mz_zip_get_error_string(err));
|
||||
}
|
||||
|
||||
JSValue jszip = JS_NewObjectClass(js, js_reader_class_id);
|
||||
JS_SetOpaque(jszip, zip);
|
||||
return jszip;
|
||||
}
|
||||
|
||||
static JSValue js_miniz_write(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
const char *file = JS_ToCString(js, argv[0]);
|
||||
if (!file)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
mz_zip_archive *zip = calloc(sizeof(*zip), 1);
|
||||
if (!zip) {
|
||||
JS_FreeCString(js, file);
|
||||
return JS_ThrowOutOfMemory(js);
|
||||
}
|
||||
|
||||
mz_bool success = mz_zip_writer_init_file(zip, file, 0);
|
||||
JS_FreeCString(js, file);
|
||||
|
||||
if (!success) {
|
||||
int err = mz_zip_get_last_error(zip);
|
||||
mz_zip_writer_end(zip);
|
||||
free(zip);
|
||||
return JS_ThrowInternalError(js, "Failed to initialize zip writer: %s", mz_zip_get_error_string(err));
|
||||
}
|
||||
|
||||
JSValue jszip = JS_NewObjectClass(js, js_writer_class_id);
|
||||
JS_SetOpaque(jszip, zip);
|
||||
return jszip;
|
||||
}
|
||||
|
||||
static JSValue js_miniz_compress(JSContext *js, JSValue this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(js,
|
||||
"compress needs a string or ArrayBuffer");
|
||||
|
||||
/* ─── 1. Grab the input data ──────────────────────────────── */
|
||||
const char *cstring = NULL;
|
||||
size_t in_len = 0;
|
||||
const void *in_ptr = NULL;
|
||||
|
||||
if (JS_IsString(argv[0])) {
|
||||
/* String → UTF-8 bytes without the terminating NUL */
|
||||
cstring = JS_ToCStringLen(js, &in_len, argv[0]);
|
||||
if (!cstring)
|
||||
return JS_EXCEPTION;
|
||||
in_ptr = cstring;
|
||||
} else {
|
||||
in_ptr = js_get_blob_data(js, &in_len, argv[0]);
|
||||
if (in_ptr == -1)
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
|
||||
/* ─── 2. Allocate an output buffer big enough ────────────── */
|
||||
mz_ulong out_len_est = mz_compressBound(in_len);
|
||||
void *out_buf = js_malloc(js, out_len_est);
|
||||
if (!out_buf) {
|
||||
if (cstring) JS_FreeCString(js, cstring);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
|
||||
/* ─── 3. Do the compression (MZ_DEFAULT_COMPRESSION = level 6) */
|
||||
mz_ulong out_len = out_len_est;
|
||||
int st = mz_compress2(out_buf, &out_len,
|
||||
in_ptr, in_len, MZ_DEFAULT_COMPRESSION);
|
||||
|
||||
/* clean-up for string input */
|
||||
if (cstring) JS_FreeCString(js, cstring);
|
||||
|
||||
if (st != MZ_OK) {
|
||||
js_free(js, out_buf);
|
||||
return JS_ThrowInternalError(js,
|
||||
"miniz: compression failed (%d)", st);
|
||||
}
|
||||
|
||||
/* ─── 4. Hand JavaScript a copy of the compressed data ────── */
|
||||
JSValue abuf = js_new_blob_stoned_copy(js, out_buf, out_len);
|
||||
js_free(js, out_buf);
|
||||
return abuf;
|
||||
}
|
||||
|
||||
static JSValue js_miniz_decompress(JSContext *js,
|
||||
JSValueConst this_val,
|
||||
int argc,
|
||||
JSValueConst *argv)
|
||||
{
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(js,
|
||||
"decompress: need compressed ArrayBuffer");
|
||||
|
||||
/* grab compressed data */
|
||||
size_t in_len;
|
||||
void *in_ptr = js_get_blob_data(js, &in_len, argv[0]);
|
||||
if (in_ptr == -1)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
/* zlib header present → tell tinfl to parse it */
|
||||
size_t out_len = 0;
|
||||
void *out_ptr = tinfl_decompress_mem_to_heap(
|
||||
in_ptr, in_len, &out_len,
|
||||
TINFL_FLAG_PARSE_ZLIB_HEADER);
|
||||
|
||||
if (!out_ptr)
|
||||
return JS_ThrowInternalError(js,
|
||||
"miniz: decompression failed");
|
||||
|
||||
JSValue ret;
|
||||
ret = JS_NewStringLen(js, (const char *)out_ptr, out_len);
|
||||
#ifdef MZ_FREE
|
||||
MZ_FREE(out_ptr);
|
||||
#else
|
||||
free(out_ptr);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static const JSCFunctionListEntry js_miniz_funcs[] = {
|
||||
JS_CFUNC_DEF("read", 1, js_miniz_read),
|
||||
JS_CFUNC_DEF("write", 1, js_miniz_write),
|
||||
JS_CFUNC_DEF("compress", 1, js_miniz_compress),
|
||||
JS_CFUNC_DEF("decompress", 1, js_miniz_decompress),
|
||||
};
|
||||
|
||||
JSValue js_writer_add_file(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
if (argc < 2)
|
||||
return JS_ThrowTypeError(js, "add_file requires (path, arrayBuffer)");
|
||||
|
||||
mz_zip_archive *zip = js2writer(js, self);
|
||||
const char *pathInZip = JS_ToCString(js, argv[0]);
|
||||
if (!pathInZip)
|
||||
return JS_ThrowTypeError(js, "Could not parse path argument");
|
||||
|
||||
size_t dataLen;
|
||||
void *data = js_get_blob_data(js, &dataLen, argv[1]);
|
||||
if (data == -1) {
|
||||
JS_FreeCString(js, pathInZip);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
|
||||
int success = mz_zip_writer_add_mem(zip, pathInZip, data, dataLen, MZ_DEFAULT_COMPRESSION);
|
||||
JS_FreeCString(js, pathInZip);
|
||||
|
||||
if (!success)
|
||||
return JS_ThrowInternalError(js, "Failed to add memory to zip");
|
||||
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
|
||||
static const JSCFunctionListEntry js_writer_funcs[] = {
|
||||
JS_CFUNC_DEF("add_file", 1, js_writer_add_file),
|
||||
};
|
||||
|
||||
JSValue js_reader_mod(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
const char *file = JS_ToCString(js,argv[0]);
|
||||
if (!file)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
mz_zip_archive *zip = js2reader(js, self);
|
||||
if (!zip) {
|
||||
JS_FreeCString(js, file);
|
||||
return JS_ThrowInternalError(js, "Invalid zip reader");
|
||||
}
|
||||
|
||||
mz_zip_archive_file_stat pstat;
|
||||
mz_uint index = mz_zip_reader_locate_file(zip, file, NULL, 0);
|
||||
|
||||
if (index == (mz_uint)-1) {
|
||||
JS_FreeCString(js, file);
|
||||
return JS_ThrowReferenceError(js, "File '%s' not found in archive", file);
|
||||
}
|
||||
|
||||
JS_FreeCString(js, file);
|
||||
|
||||
if (!mz_zip_reader_file_stat(zip, index, &pstat)) {
|
||||
int err = mz_zip_get_last_error(zip);
|
||||
return JS_ThrowInternalError(js, "Failed to get file stats: %s", mz_zip_get_error_string(err));
|
||||
}
|
||||
|
||||
return JS_NewFloat64(js, pstat.m_time);
|
||||
}
|
||||
|
||||
JSValue js_reader_exists(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
const char *file = JS_ToCString(js,argv[0]);
|
||||
if (!file)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
mz_zip_archive *zip = js2reader(js, self);
|
||||
if (!zip) {
|
||||
JS_FreeCString(js, file);
|
||||
return JS_ThrowInternalError(js, "Invalid zip reader");
|
||||
}
|
||||
|
||||
mz_uint index = mz_zip_reader_locate_file(zip, file, NULL, 0);
|
||||
JS_FreeCString(js,file);
|
||||
if (index == (mz_uint)-1) return JS_NewBool(js, 0);
|
||||
return JS_NewBool(js, 1);
|
||||
}
|
||||
|
||||
JSValue js_reader_slurp(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
const char *file = JS_ToCString(js,argv[0]);
|
||||
if (!file)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
mz_zip_archive *zip = js2reader(js, self);
|
||||
if (!zip) {
|
||||
JS_FreeCString(js, file);
|
||||
return JS_ThrowInternalError(js, "Invalid zip reader");
|
||||
}
|
||||
|
||||
size_t len;
|
||||
void *data = mz_zip_reader_extract_file_to_heap(zip, file, &len, 0);
|
||||
|
||||
if (!data) {
|
||||
int err = mz_zip_get_last_error(zip);
|
||||
const char *filename = file;
|
||||
JS_FreeCString(js, file);
|
||||
return JS_ThrowInternalError(js, "Failed to extract file '%s': %s", filename, mz_zip_get_error_string(err));
|
||||
}
|
||||
|
||||
JS_FreeCString(js, file);
|
||||
|
||||
JSValue ret = js_new_blob_stoned_copy(js, data, len);
|
||||
free(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSValue js_reader_list(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
mz_zip_archive *zip = js2reader(js, self);
|
||||
if (!zip)
|
||||
return JS_ThrowInternalError(js, "Invalid zip reader");
|
||||
|
||||
mz_uint num_files = mz_zip_reader_get_num_files(zip);
|
||||
|
||||
JSValue arr = JS_NewArray(js);
|
||||
if (JS_IsException(arr))
|
||||
return arr;
|
||||
|
||||
mz_uint arr_index = 0;
|
||||
for (mz_uint i = 0; i < num_files; i++) {
|
||||
mz_zip_archive_file_stat file_stat;
|
||||
if (!mz_zip_reader_file_stat(zip, i, &file_stat))
|
||||
continue;
|
||||
|
||||
JSValue filename = JS_NewString(js, file_stat.m_filename);
|
||||
if (JS_IsException(filename)) {
|
||||
JS_FreeValue(js, arr);
|
||||
return filename;
|
||||
}
|
||||
JS_SetPropertyUint32(js, arr, arr_index++, filename);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
JSValue js_reader_is_directory(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(js, "is_directory requires a file index");
|
||||
|
||||
int32_t index;
|
||||
if (JS_ToInt32(js, &index, argv[0]))
|
||||
return JS_EXCEPTION;
|
||||
|
||||
mz_zip_archive *zip = js2reader(js, self);
|
||||
if (!zip)
|
||||
return JS_ThrowInternalError(js, "Invalid zip reader");
|
||||
|
||||
return JS_NewBool(js, mz_zip_reader_is_file_a_directory(zip, index));
|
||||
}
|
||||
|
||||
JSValue js_reader_get_filename(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(js, "get_filename requires a file index");
|
||||
|
||||
int32_t index;
|
||||
if (JS_ToInt32(js, &index, argv[0]))
|
||||
return JS_EXCEPTION;
|
||||
|
||||
mz_zip_archive *zip = js2reader(js, self);
|
||||
if (!zip)
|
||||
return JS_ThrowInternalError(js, "Invalid zip reader");
|
||||
|
||||
mz_zip_archive_file_stat file_stat;
|
||||
if (!mz_zip_reader_file_stat(zip, index, &file_stat))
|
||||
return JS_ThrowInternalError(js, "Failed to get file stats");
|
||||
|
||||
return JS_NewString(js, file_stat.m_filename);
|
||||
}
|
||||
|
||||
JSValue js_reader_count(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
mz_zip_archive *zip = js2reader(js, self);
|
||||
if (!zip)
|
||||
return JS_ThrowInternalError(js, "Invalid zip reader");
|
||||
return JS_NewUint32(js, mz_zip_reader_get_num_files(zip));
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_reader_funcs[] = {
|
||||
JS_CFUNC_DEF("mod", 1, js_reader_mod),
|
||||
JS_CFUNC_DEF("exists", 1, js_reader_exists),
|
||||
JS_CFUNC_DEF("slurp", 1, js_reader_slurp),
|
||||
JS_CFUNC_DEF("list", 0, js_reader_list),
|
||||
JS_CFUNC_DEF("is_directory", 1, js_reader_is_directory),
|
||||
JS_CFUNC_DEF("get_filename", 1, js_reader_get_filename),
|
||||
JS_CFUNC_DEF("count", 0, js_reader_count),
|
||||
};
|
||||
|
||||
JSValue js_miniz_use(JSContext *js)
|
||||
{
|
||||
JS_NewClassID(&js_reader_class_id);
|
||||
JS_NewClass(JS_GetRuntime(js), js_reader_class_id, &js_reader_class);
|
||||
JSValue reader_proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, reader_proto, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry));
|
||||
JS_SetClassProto(js, js_reader_class_id, reader_proto);
|
||||
|
||||
JS_NewClassID(&js_writer_class_id);
|
||||
JS_NewClass(JS_GetRuntime(js), js_writer_class_id, &js_writer_class);
|
||||
JSValue writer_proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, writer_proto, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry));
|
||||
JS_SetClassProto(js, js_writer_class_id, writer_proto);
|
||||
|
||||
JSValue export = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, export, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry));
|
||||
return export;
|
||||
}
|
||||
Reference in New Issue
Block a user