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:
Bob Nystrom
2016-02-20 17:23:42 +00:00
parent 38f48fbce1
commit 60fc3ff59c
21 changed files with 463 additions and 52 deletions
+6 -2
View File
@@ -11,12 +11,14 @@
extern void directoryList(WrenVM* vm);
extern void fileAllocate(WrenVM* vm);
extern void fileFinalize(void* data);
extern void fileDelete(WrenVM* vm);
extern void fileOpen(WrenVM* vm);
extern void fileSizePath(WrenVM* vm);
extern void fileClose(WrenVM* vm);
extern void fileDescriptor(WrenVM* vm);
extern void fileReadBytes(WrenVM* vm);
extern void fileSize(WrenVM* vm);
extern void fileWriteBytes(WrenVM* vm);
extern void processAllArguments(WrenVM* vm);
extern void statPath(WrenVM* vm);
extern void stdinReadStart(WrenVM* vm);
@@ -32,7 +34,7 @@ extern void timerStartTimer(WrenVM* vm);
// If you add a new method to the longest class below, make sure to bump this.
// Note that it also includes an extra slot for the sentinel value indicating
// the end of the list.
#define MAX_METHODS_PER_CLASS 10
#define MAX_METHODS_PER_CLASS 11
// The maximum number of foreign classes a single built-in module defines.
//
@@ -98,12 +100,14 @@ static ModuleRegistry modules[] =
CLASS(File)
STATIC_METHOD("<allocate>", fileAllocate)
FINALIZER(fileFinalize)
STATIC_METHOD("open_(_,_)", fileOpen)
STATIC_METHOD("delete_(_,_)", fileDelete)
STATIC_METHOD("open_(_,_,_)", fileOpen)
STATIC_METHOD("sizePath_(_,_)", fileSizePath)
METHOD("close_(_)", fileClose)
METHOD("descriptor", fileDescriptor)
METHOD("readBytes_(_,_,_)", fileReadBytes)
METHOD("size_(_)", fileSize)
METHOD("writeBytes_(_,_,_)", fileWriteBytes)
END_CLASS
CLASS(Stat)
STATIC_METHOD("path_(_,_)", statPath)
+52 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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"