feat: add file create, delete, writeBytes and FileFlags class to IO module
Implement File.create() and File.delete() static methods with async libuv operations, add writeBytes() instance method for writing at offsets, introduce FileFlags constants class mirroring POSIX open() flags, refactor File.open() to accept flag parameters via openWithFlags(), and update documentation and module registration accordingly.
This commit is contained in:
+52
-7
@@ -124,7 +124,6 @@ static void directoryListCallback(uv_fs_t* request)
|
||||
void directoryList(WrenVM* vm)
|
||||
{
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
|
||||
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2));
|
||||
|
||||
// TODO: Check return.
|
||||
@@ -151,6 +150,21 @@ void fileFinalize(void* data)
|
||||
uv_fs_req_cleanup(&request);
|
||||
}
|
||||
|
||||
static void fileDeleteCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handleRequestError(request)) return;
|
||||
schedulerResume(freeRequest(request), false);
|
||||
}
|
||||
|
||||
void fileDelete(WrenVM* vm)
|
||||
{
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2));
|
||||
|
||||
// TODO: Check return.
|
||||
uv_fs_unlink(getLoop(), request, path, fileDeleteCallback);
|
||||
}
|
||||
|
||||
static void fileOpenCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handleRequestError(request)) return;
|
||||
@@ -164,10 +178,12 @@ static void fileOpenCallback(uv_fs_t* request)
|
||||
void fileOpen(WrenVM* vm)
|
||||
{
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2));
|
||||
int flags = (int)wrenGetSlotDouble(vm, 2);
|
||||
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 3));
|
||||
|
||||
// TODO: Allow controlling flags and modes.
|
||||
uv_fs_open(getLoop(), request, path, O_RDONLY, 0, fileOpenCallback);
|
||||
// TODO: Allow controlling access.
|
||||
uv_fs_open(getLoop(), request, path, flags, S_IRUSR | S_IWUSR,
|
||||
fileOpenCallback);
|
||||
}
|
||||
|
||||
// Called by libuv when the stat call for size completes.
|
||||
@@ -261,14 +277,43 @@ void fileReadBytes(WrenVM* vm)
|
||||
|
||||
void fileSize(WrenVM* vm)
|
||||
{
|
||||
int fd = *(int*)wrenGetSlotForeign(vm, 0);
|
||||
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 1));
|
||||
|
||||
int fd = *(int*)wrenGetSlotForeign(vm, 0);
|
||||
// TODO: Assert fd != -1.
|
||||
|
||||
uv_fs_fstat(getLoop(), request, fd, fileSizeCallback);
|
||||
}
|
||||
|
||||
static void fileWriteBytesCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handleRequestError(request)) return;
|
||||
|
||||
FileRequestData* data = (FileRequestData*)request->data;
|
||||
free(data->buffer.base);
|
||||
|
||||
schedulerResume(freeRequest(request), false);
|
||||
}
|
||||
|
||||
void fileWriteBytes(WrenVM* vm)
|
||||
{
|
||||
int fd = *(int*)wrenGetSlotForeign(vm, 0);
|
||||
int length;
|
||||
const char* bytes = wrenGetSlotBytes(vm, 1, &length);
|
||||
size_t offset = (size_t)wrenGetSlotDouble(vm, 2);
|
||||
uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 3));
|
||||
|
||||
FileRequestData* data = (FileRequestData*)request->data;
|
||||
|
||||
data->buffer.len = length;
|
||||
// TODO: Instead of copying, just create a WrenValue for the byte string and
|
||||
// hold on to it in the request until the write is done.
|
||||
// TODO: Handle allocation failure.
|
||||
data->buffer.base = (char*)malloc(length);
|
||||
memcpy(data->buffer.base, bytes, length);
|
||||
|
||||
uv_fs_write(getLoop(), request, fd, &data->buffer, 1, offset,
|
||||
fileWriteBytesCallback);
|
||||
}
|
||||
|
||||
// Called by libuv when the stat call completes.
|
||||
static void statPathCallback(uv_fs_t* request)
|
||||
{
|
||||
|
||||
+80
-20
@@ -1,9 +1,13 @@
|
||||
import "scheduler" for Scheduler
|
||||
|
||||
class Directory {
|
||||
static list(path) {
|
||||
// TODO: Copied from File. Figure out good way to share this.
|
||||
static ensurePath_(path) {
|
||||
if (!(path is String)) Fiber.abort("Path must be a string.")
|
||||
}
|
||||
|
||||
static list(path) {
|
||||
ensurePath_(path)
|
||||
list_(path, Fiber.current)
|
||||
return Scheduler.runNextScheduled_()
|
||||
}
|
||||
@@ -12,16 +16,42 @@ class Directory {
|
||||
}
|
||||
|
||||
foreign class File {
|
||||
static open(path) {
|
||||
if (!(path is String)) Fiber.abort("Path must be a string.")
|
||||
static create(path) {
|
||||
return openWithFlags(path,
|
||||
FileFlags.writeOnly |
|
||||
FileFlags.create |
|
||||
FileFlags.truncate)
|
||||
}
|
||||
|
||||
open_(path, Fiber.current)
|
||||
static create(path, fn) {
|
||||
return openWithFlags(path,
|
||||
FileFlags.writeOnly |
|
||||
FileFlags.create |
|
||||
FileFlags.truncate, fn)
|
||||
}
|
||||
|
||||
static delete(path) {
|
||||
File.ensurePath_(path)
|
||||
delete_(path, Fiber.current)
|
||||
return Scheduler.runNextScheduled_()
|
||||
}
|
||||
|
||||
static open(path) { openWithFlags(path, FileFlags.readOnly) }
|
||||
|
||||
static open(path, fn) { openWithFlags(path, FileFlags.readOnly, fn) }
|
||||
|
||||
// TODO: Add named parameters and then call this "open(_,flags:_)"?
|
||||
// TODO: Test.
|
||||
static openWithFlags(path, flags) {
|
||||
File.ensurePath_(path)
|
||||
File.ensureInt_(flags, "Flags")
|
||||
open_(path, flags, Fiber.current)
|
||||
var fd = Scheduler.runNextScheduled_()
|
||||
return new_(fd)
|
||||
}
|
||||
|
||||
static open(path, fn) {
|
||||
var file = open(path)
|
||||
static openWithFlags(path, flags, fn) {
|
||||
var file = openWithFlags(path, flags)
|
||||
var fiber = Fiber.new { fn.call(file) }
|
||||
|
||||
// Poor man's finally. Can we make this more elegant?
|
||||
@@ -38,8 +68,7 @@ foreign class File {
|
||||
}
|
||||
|
||||
static size(path) {
|
||||
if (!(path is String)) Fiber.abort("Path must be a string.")
|
||||
|
||||
File.ensurePath_(path)
|
||||
sizePath_(path, Fiber.current)
|
||||
return Scheduler.runNextScheduled_()
|
||||
}
|
||||
@@ -56,8 +85,7 @@ foreign class File {
|
||||
isOpen { descriptor != -1 }
|
||||
|
||||
size {
|
||||
if (!isOpen) Fiber.abort("File is not open.")
|
||||
|
||||
ensureOpen_()
|
||||
size_(Fiber.current)
|
||||
return Scheduler.runNextScheduled_()
|
||||
}
|
||||
@@ -65,25 +93,57 @@ foreign class File {
|
||||
readBytes(count) { readBytes(count, 0) }
|
||||
|
||||
readBytes(count, offset) {
|
||||
if (!isOpen) Fiber.abort("File is not open.")
|
||||
if (!(count is Num)) Fiber.abort("Count must be an integer.")
|
||||
if (!count.isInteger) Fiber.abort("Count must be an integer.")
|
||||
if (count < 0) Fiber.abort("Count cannot be negative.")
|
||||
|
||||
if (!(offset is Num)) Fiber.abort("Offset must be an integer.")
|
||||
if (!offset.isInteger) Fiber.abort("Offset must be an integer.")
|
||||
if (offset < 0) Fiber.abort("Offset cannot be negative.")
|
||||
ensureOpen_()
|
||||
File.ensureInt_(count, "Count")
|
||||
File.ensureInt_(offset, "Offset")
|
||||
|
||||
readBytes_(count, offset, Fiber.current)
|
||||
return Scheduler.runNextScheduled_()
|
||||
}
|
||||
|
||||
foreign static open_(path, fiber)
|
||||
writeBytes(bytes) { writeBytes(bytes, size) }
|
||||
|
||||
writeBytes(bytes, offset) {
|
||||
ensureOpen_()
|
||||
if (!(bytes is String)) Fiber.abort("Bytes must be a string.")
|
||||
File.ensureInt_(offset, "Offset")
|
||||
|
||||
writeBytes_(bytes, offset, Fiber.current)
|
||||
return Scheduler.runNextScheduled_()
|
||||
}
|
||||
|
||||
ensureOpen_() {
|
||||
if (!isOpen) Fiber.abort("File is not open.")
|
||||
}
|
||||
|
||||
static ensurePath_(path) {
|
||||
if (!(path is String)) Fiber.abort("Path must be a string.")
|
||||
}
|
||||
|
||||
static ensureInt_(value, name) {
|
||||
if (!(value is Num)) Fiber.abort("%(name) must be an integer.")
|
||||
if (!value.isInteger) Fiber.abort("%(name) must be an integer.")
|
||||
if (value < 0) Fiber.abort("%(name) cannot be negative.")
|
||||
}
|
||||
|
||||
foreign static delete_(path, fiber)
|
||||
foreign static open_(path, flags, fiber)
|
||||
foreign static sizePath_(path, fiber)
|
||||
|
||||
foreign close_(fiber)
|
||||
foreign readBytes_(count, start, fiber)
|
||||
foreign readBytes_(count, offset, fiber)
|
||||
foreign size_(fiber)
|
||||
foreign writeBytes_(bytes, offset, fiber)
|
||||
}
|
||||
|
||||
class FileFlags {
|
||||
static readOnly { 0x0000 }
|
||||
static writeOnly { 0x0001 }
|
||||
static readWrite { 0x0002 }
|
||||
static sync { 0x0080 }
|
||||
static create { 0x0200 }
|
||||
static truncate { 0x0400 }
|
||||
static exclusive { 0x0400 }
|
||||
}
|
||||
|
||||
class Stat {
|
||||
|
||||
+80
-20
@@ -3,9 +3,13 @@ static const char* ioModuleSource =
|
||||
"import \"scheduler\" for Scheduler\n"
|
||||
"\n"
|
||||
"class Directory {\n"
|
||||
" static list(path) {\n"
|
||||
" // TODO: Copied from File. Figure out good way to share this.\n"
|
||||
" static ensurePath_(path) {\n"
|
||||
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static list(path) {\n"
|
||||
" ensurePath_(path)\n"
|
||||
" list_(path, Fiber.current)\n"
|
||||
" return Scheduler.runNextScheduled_()\n"
|
||||
" }\n"
|
||||
@@ -14,16 +18,42 @@ static const char* ioModuleSource =
|
||||
"}\n"
|
||||
"\n"
|
||||
"foreign class File {\n"
|
||||
" static open(path) {\n"
|
||||
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
|
||||
" static create(path) {\n"
|
||||
" return openWithFlags(path,\n"
|
||||
" FileFlags.writeOnly |\n"
|
||||
" FileFlags.create |\n"
|
||||
" FileFlags.truncate)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" open_(path, Fiber.current)\n"
|
||||
" static create(path, fn) {\n"
|
||||
" return openWithFlags(path,\n"
|
||||
" FileFlags.writeOnly |\n"
|
||||
" FileFlags.create |\n"
|
||||
" FileFlags.truncate, fn)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static delete(path) {\n"
|
||||
" File.ensurePath_(path)\n"
|
||||
" delete_(path, Fiber.current)\n"
|
||||
" return Scheduler.runNextScheduled_()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static open(path) { openWithFlags(path, FileFlags.readOnly) }\n"
|
||||
"\n"
|
||||
" static open(path, fn) { openWithFlags(path, FileFlags.readOnly, fn) }\n"
|
||||
"\n"
|
||||
" // TODO: Add named parameters and then call this \"open(_,flags:_)\"?\n"
|
||||
" // TODO: Test.\n"
|
||||
" static openWithFlags(path, flags) {\n"
|
||||
" File.ensurePath_(path)\n"
|
||||
" File.ensureInt_(flags, \"Flags\")\n"
|
||||
" open_(path, flags, Fiber.current)\n"
|
||||
" var fd = Scheduler.runNextScheduled_()\n"
|
||||
" return new_(fd)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static open(path, fn) {\n"
|
||||
" var file = open(path)\n"
|
||||
" static openWithFlags(path, flags, fn) {\n"
|
||||
" var file = openWithFlags(path, flags)\n"
|
||||
" var fiber = Fiber.new { fn.call(file) }\n"
|
||||
"\n"
|
||||
" // Poor man's finally. Can we make this more elegant?\n"
|
||||
@@ -40,8 +70,7 @@ static const char* ioModuleSource =
|
||||
" }\n"
|
||||
"\n"
|
||||
" static size(path) {\n"
|
||||
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
|
||||
"\n"
|
||||
" File.ensurePath_(path)\n"
|
||||
" sizePath_(path, Fiber.current)\n"
|
||||
" return Scheduler.runNextScheduled_()\n"
|
||||
" }\n"
|
||||
@@ -58,8 +87,7 @@ static const char* ioModuleSource =
|
||||
" isOpen { descriptor != -1 }\n"
|
||||
"\n"
|
||||
" size {\n"
|
||||
" if (!isOpen) Fiber.abort(\"File is not open.\")\n"
|
||||
"\n"
|
||||
" ensureOpen_()\n"
|
||||
" size_(Fiber.current)\n"
|
||||
" return Scheduler.runNextScheduled_()\n"
|
||||
" }\n"
|
||||
@@ -67,25 +95,57 @@ static const char* ioModuleSource =
|
||||
" readBytes(count) { readBytes(count, 0) }\n"
|
||||
"\n"
|
||||
" readBytes(count, offset) {\n"
|
||||
" if (!isOpen) Fiber.abort(\"File is not open.\")\n"
|
||||
" if (!(count is Num)) Fiber.abort(\"Count must be an integer.\")\n"
|
||||
" if (!count.isInteger) Fiber.abort(\"Count must be an integer.\")\n"
|
||||
" if (count < 0) Fiber.abort(\"Count cannot be negative.\")\n"
|
||||
"\n"
|
||||
" if (!(offset is Num)) Fiber.abort(\"Offset must be an integer.\")\n"
|
||||
" if (!offset.isInteger) Fiber.abort(\"Offset must be an integer.\")\n"
|
||||
" if (offset < 0) Fiber.abort(\"Offset cannot be negative.\")\n"
|
||||
" ensureOpen_()\n"
|
||||
" File.ensureInt_(count, \"Count\")\n"
|
||||
" File.ensureInt_(offset, \"Offset\")\n"
|
||||
"\n"
|
||||
" readBytes_(count, offset, Fiber.current)\n"
|
||||
" return Scheduler.runNextScheduled_()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static open_(path, fiber)\n"
|
||||
" writeBytes(bytes) { writeBytes(bytes, size) }\n"
|
||||
"\n"
|
||||
" writeBytes(bytes, offset) {\n"
|
||||
" ensureOpen_()\n"
|
||||
" if (!(bytes is String)) Fiber.abort(\"Bytes must be a string.\")\n"
|
||||
" File.ensureInt_(offset, \"Offset\")\n"
|
||||
"\n"
|
||||
" writeBytes_(bytes, offset, Fiber.current)\n"
|
||||
" return Scheduler.runNextScheduled_()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" ensureOpen_() {\n"
|
||||
" if (!isOpen) Fiber.abort(\"File is not open.\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static ensurePath_(path) {\n"
|
||||
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static ensureInt_(value, name) {\n"
|
||||
" if (!(value is Num)) Fiber.abort(\"%(name) must be an integer.\")\n"
|
||||
" if (!value.isInteger) Fiber.abort(\"%(name) must be an integer.\")\n"
|
||||
" if (value < 0) Fiber.abort(\"%(name) cannot be negative.\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static delete_(path, fiber)\n"
|
||||
" foreign static open_(path, flags, fiber)\n"
|
||||
" foreign static sizePath_(path, fiber)\n"
|
||||
"\n"
|
||||
" foreign close_(fiber)\n"
|
||||
" foreign readBytes_(count, start, fiber)\n"
|
||||
" foreign readBytes_(count, offset, fiber)\n"
|
||||
" foreign size_(fiber)\n"
|
||||
" foreign writeBytes_(bytes, offset, fiber)\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class FileFlags {\n"
|
||||
" static readOnly { 0x0000 }\n"
|
||||
" static writeOnly { 0x0001 }\n"
|
||||
" static readWrite { 0x0002 }\n"
|
||||
" static sync { 0x0080 }\n"
|
||||
" static create { 0x0200 }\n"
|
||||
" static truncate { 0x0400 }\n"
|
||||
" static exclusive { 0x0400 }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Stat {\n"
|
||||
|
||||
Reference in New Issue
Block a user