From 07a082862625f1309d47d449f36490a21d4b0f30 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Wed, 27 Oct 2021 23:27:21 +0000 Subject: [PATCH] Async File.writeFile. git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3673 ed5197a5-7fde-0310-b194-c3ffbd925b24 --- core/core.js | 9 ++--- src/file.js.c | 110 ++++++++++++++++++++++++++++++++++++++------------ src/tests.c | 14 +++++++ 3 files changed, 102 insertions(+), 31 deletions(-) diff --git a/core/core.js b/core/core.js index 9ed87d14..9f0e316a 100644 --- a/core/core.js +++ b/core/core.js @@ -272,11 +272,10 @@ function getGlobalSettings() { function setGlobalSettings(settings) { makeDirectoryForFile(kGlobalSettingsFile); - if (!File.writeFile(kGlobalSettingsFile, JSON.stringify(settings))) { - gGlobalSettings = settings; - } else { - throw new Error("Unable to save settings."); - } + gGlobalSettings = settings; + return File.writeFile(kGlobalSettingsFile, JSON.stringify(settings)).catch(function(error) { + throw new Error("Unable to save settings: " + error); + }); } var kStaticFiles = [ diff --git a/src/file.js.c b/src/file.js.c index 9336958f..1236919d 100644 --- a/src/file.js.c +++ b/src/file.js.c @@ -4,6 +4,7 @@ #include #include +#include #include #ifdef _WIN32 @@ -36,19 +37,21 @@ void tf_file_register(JSContext* context) JSValue global = JS_GetGlobalObject(context); JSValue file = JS_NewObject(context); JS_SetPropertyStr(context, global, "File", file); + JS_SetPropertyStr(context, file, "readFile", JS_NewCFunction(context, _file_read_file, "readFile", 1)); + JS_SetPropertyStr(context, file, "writeFile", JS_NewCFunction(context, _file_write_file, "writeFile", 2)); + + /* TODO: async */ JS_SetPropertyStr(context, file, "makeDirectory", JS_NewCFunction(context, _file_make_directory, "makeDirectory", 1)); JS_SetPropertyStr(context, file, "readDirectory", JS_NewCFunction(context, _file_read_directory, "readDirectory", 1)); - JS_SetPropertyStr(context, file, "readFile", JS_NewCFunction(context, _file_read_file, "readFile", 1)); JS_SetPropertyStr(context, file, "renameFile", JS_NewCFunction(context, _file_rename_file, "renameFile", 2)); JS_SetPropertyStr(context, file, "stat", JS_NewCFunction(context, _file_stat, "stat", 1)); JS_SetPropertyStr(context, file, "unlinkFile", JS_NewCFunction(context, _file_unlink_file, "unlinkFile", 1)); - JS_SetPropertyStr(context, file, "writeFile", JS_NewCFunction(context, _file_write_file, "writeFile", 2)); JS_FreeValue(context, global); } static const int k_file_read_max = 4 * 1024 * 1024; -static void _file_read_close_callback(uv_fs_t* req) +static void _file_async_close_callback(uv_fs_t* req) { uv_fs_req_cleanup(req); free(req); @@ -76,7 +79,7 @@ static void _file_read_read_callback(uv_fs_t* req) { tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, uv_strerror(req->result))); } - int result = uv_fs_close(req->loop, req, req->file, _file_read_close_callback); + int result = uv_fs_close(req->loop, req, req->file, _file_async_close_callback); if (result < 0) { free(req); @@ -97,7 +100,7 @@ static void _file_read_open_callback(uv_fs_t* req) if (result < 0) { tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, uv_strerror(result))); - result = uv_fs_close(req->loop, req, file, _file_read_close_callback); + result = uv_fs_close(req->loop, req, file, _file_async_close_callback); } } else @@ -127,32 +130,87 @@ static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int ar return tf_task_get_promise(task, promise); } +static void _file_write_write_callback(uv_fs_t* req) +{ + uv_fs_req_cleanup(req); + tf_task_t* task = req->loop->data; + JSContext* context = tf_task_get_context(task); + promiseid_t promise = (promiseid_t)(intptr_t)req->data; + if (req->result >= 0) + { + tf_task_resolve_promise(task, promise, JS_NewInt64(context, req->result)); + } + else + { + tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, uv_strerror(req->result))); + } + uv_fs_close(req->loop, req, req->file, _file_async_close_callback); +} + +static void _file_write_open_callback(uv_fs_t* req) +{ + uv_fs_req_cleanup(req); + tf_task_t* task = req->loop->data; + JSContext* context = tf_task_get_context(task); + promiseid_t promise = (promiseid_t)(intptr_t)req->data; + if (req->result >= 0) + { + size_t size = 0; + memcpy(&size, req + 1, sizeof(size)); + uv_buf_t buf = { .base = (char*)(req + 1) + sizeof(size), .len = size }; + uv_file file = req->result; + int result = uv_fs_write(req->loop, req, file, &buf, 1, 0, _file_write_write_callback); + if (result < 0) + { + tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, uv_strerror(result))); + result = uv_fs_close(req->loop, req, file, _file_async_close_callback); + } + } + else + { + tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, uv_strerror(req->result))); + free(req); + } +} + static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { - JSValue result = JS_NULL; - const char* fileName = JS_ToCString(context, argv[0]); - FILE* file = fopen(fileName, "wb"); - JS_FreeCString(context, fileName); + void* task = JS_GetContextOpaque(context); + const char* file_name = JS_ToCString(context, argv[0]); - if (file) + size_t size; + uint8_t* buffer = tf_try_get_array_buffer(context, &size, argv[1]); + bool is_array_buffer = false; + if (buffer) { - size_t size; - uint8_t* buffer = tf_try_get_array_buffer(context, &size, argv[1]); - if (buffer) - { - int written = fwrite((const char*)buffer, 1, size, file); - result = JS_NewInt32(context, (size_t)written == size ? 0 : written); - } - else - { - const char* data = JS_ToCStringLen(context, &size, argv[1]); - int written = fwrite((const char*)data, 1, size, file); - result = JS_NewInt32(context, (size_t)written == size ? 0 : written); - JS_FreeCString(context, data); - } - fclose(file); + is_array_buffer = true; } - return result; + else + { + buffer = (uint8_t*)JS_ToCStringLen(context, &size, argv[1]); + } + + promiseid_t promise = tf_task_allocate_promise(task); + JSValue promise_value = tf_task_get_promise(task, promise); + uv_fs_t* req = malloc(sizeof(uv_fs_t) + sizeof(size_t) + size); + *req = (uv_fs_t) + { + .data = (void*)(intptr_t)promise, + }; + memcpy(req + 1, &size, sizeof(size_t)); + memcpy((char*)(req + 1) + sizeof(size_t), buffer, size); + if (!is_array_buffer) + { + JS_FreeCString(context, (const char*)buffer); + } + + int result = uv_fs_open(tf_task_get_loop(task), req, file_name, UV_FS_O_CREAT | UV_FS_O_WRONLY, 0755, _file_write_open_callback); + if (result < 0) + { + tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, uv_strerror(result))); + } + JS_FreeCString(context, file_name); + return promise_value; } static JSValue _file_rename_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) diff --git a/src/tests.c b/src/tests.c index 5d3672d1..cb8344cb 100644 --- a/src/tests.c +++ b/src/tests.c @@ -506,6 +506,20 @@ static void _test_file(const tf_test_options_t* options) " exit(1);\n" "}).catch(function(error) {\n" " print('expected error', error);\n" + "});\n" + "File.writeFile('out/new.txt', 'hello').then(function(result) {\n" + " File.readFile('out/new.txt').then(function(data) {\n" + " print('READ', utf8Decode(data));\n" + " if (utf8Decode(data) != 'hello') {\n" + " exit(1);\n" + " }\n" + " }).catch(function(error) {\n" + " print('unexpected read error', error);\n" + " exit(1);\n" + " });\n" + "}).catch(function(error) {\n" + " print('unexpected write error', error);\n" + " exit(1);\n" "});\n"); fclose(file);