From 597ee38ef28d040cbf4d629cf3d2bd3e89a70a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 28 Mar 2019 04:29:36 +0100 Subject: [PATCH] Rewrite readFile and writeFile (#2000) Using open/read/write --- cli/msg.fbs | 21 ----- cli/ops.rs | 71 ----------------- js/buffer.ts | 49 ++++++++++-- js/buffer_test.ts | 29 ++++++- js/deno.ts | 9 ++- js/files.ts | 194 ++++++++++++++++++++++++++++++++++++++-------- js/files_test.ts | 36 +++++++++ js/io.ts | 11 +++ js/read_file.ts | 37 +++------ js/write_file.ts | 70 +++++++++-------- 10 files changed, 337 insertions(+), 190 deletions(-) diff --git a/cli/msg.fbs b/cli/msg.fbs index 243034cfbc..e6b860908e 100644 --- a/cli/msg.fbs +++ b/cli/msg.fbs @@ -39,8 +39,6 @@ union Any { Read, ReadDir, ReadDirRes, - ReadFile, - ReadFileRes, ReadRes, Readlink, ReadlinkRes, @@ -69,7 +67,6 @@ union Any { WorkerGetMessageRes, WorkerPostMessage, Write, - WriteFile, WriteRes, } @@ -296,14 +293,6 @@ table Remove { recursive: bool; } -table ReadFile { - filename: string; -} - -table ReadFileRes { - data: [ubyte]; -} - table ReadDir { path: string; } @@ -312,16 +301,6 @@ table ReadDirRes { entries: [StatRes]; } -table WriteFile { - filename: string; - data: [ubyte]; - update_perm: bool; - perm: uint; - // perm specified by https://godoc.org/os#FileMode - is_create: bool; - is_append: bool; -} - table CopyFile { from: string; to: string; diff --git a/cli/ops.rs b/cli/ops.rs index 68e593934a..8f5304793c 100644 --- a/cli/ops.rs +++ b/cli/ops.rs @@ -174,7 +174,6 @@ pub fn op_selector_std(inner_type: msg::Any) -> Option { msg::Any::Permissions => Some(op_permissions), msg::Any::Read => Some(op_read), msg::Any::ReadDir => Some(op_read_dir), - msg::Any::ReadFile => Some(op_read_file), msg::Any::Readlink => Some(op_read_link), msg::Any::Remove => Some(op_remove), msg::Any::Rename => Some(op_rename), @@ -193,7 +192,6 @@ pub fn op_selector_std(inner_type: msg::Any) -> Option { msg::Any::WorkerGetMessage => Some(op_worker_get_message), msg::Any::WorkerPostMessage => Some(op_worker_post_message), msg::Any::Write => Some(op_write), - msg::Any::WriteFile => Some(op_write_file), _ => None, } } @@ -1026,45 +1024,6 @@ fn op_remove( }) } -// Prototype https://github.com/denoland/deno/blob/golang/os.go#L171-L184 -fn op_read_file( - sc: &IsolateStateContainer, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_read_file().unwrap(); - let cmd_id = base.cmd_id(); - let filename_ = inner.filename().unwrap(); - let filename = PathBuf::from(filename_); - debug!("op_read_file {}", filename.display()); - if let Err(e) = sc.state().check_read(&filename_) { - return odd_future(e); - } - blocking(base.sync(), move || { - let vec = fs::read(&filename)?; - // Build the response message. memcpy data into inner. - // TODO(ry) zero-copy. - let builder = &mut FlatBufferBuilder::new(); - let data_off = builder.create_vector(vec.as_slice()); - let inner = msg::ReadFileRes::create( - builder, - &msg::ReadFileResArgs { - data: Some(data_off), - }, - ); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::ReadFileRes, - ..Default::default() - }, - )) - }) -} - fn op_copy_file( sc: &IsolateStateContainer, base: &msg::Base<'_>, @@ -1260,36 +1219,6 @@ fn op_read_dir( }) } -fn op_write_file( - sc: &IsolateStateContainer, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - let inner = base.inner_as_write_file().unwrap(); - let filename = String::from(inner.filename().unwrap()); - let update_perm = inner.update_perm(); - let perm = inner.perm(); - let is_create = inner.is_create(); - let is_append = inner.is_append(); - - if let Err(e) = sc.state().check_write(&filename) { - return odd_future(e); - } - - blocking(base.sync(), move || -> OpResult { - debug!("op_write_file {} {}", filename, data.len()); - deno_fs::write_file_2( - Path::new(&filename), - data, - update_perm, - perm, - is_create, - is_append, - )?; - Ok(empty_buf()) - }) -} - fn op_rename( sc: &IsolateStateContainer, base: &msg::Base<'_>, diff --git a/js/buffer.ts b/js/buffer.ts index c5be762f5b..551e8c4d4a 100644 --- a/js/buffer.ts +++ b/js/buffer.ts @@ -5,7 +5,7 @@ // https://github.com/golang/go/blob/master/LICENSE //import * as io from "./io"; -import { Reader, Writer, ReadResult } from "./io"; +import { Reader, Writer, ReadResult, SyncReader, SyncWriter } from "./io"; import { assert } from "./util"; import { TextDecoder } from "./text_encoding"; import { DenoError, ErrorKind } from "./errors"; @@ -32,7 +32,7 @@ function copyBytes(dst: Uint8Array, src: Uint8Array, off = 0): number { /** A Buffer is a variable-sized buffer of bytes with read() and write() * methods. Based on https://golang.org/pkg/bytes/#Buffer */ -export class Buffer implements Reader, Writer { +export class Buffer implements Reader, SyncReader, Writer, SyncWriter { private buf: Uint8Array; // contents are the bytes buf[off : len(buf)] private off = 0; // read at buf[off], write at buf[buf.byteLength] @@ -126,11 +126,11 @@ export class Buffer implements Reader, Writer { this.buf = new Uint8Array(this.buf.buffer, 0, len); } - /** read() reads the next len(p) bytes from the buffer or until the buffer + /** readSync() reads the next len(p) bytes from the buffer or until the buffer * is drained. The return value n is the number of bytes read. If the * buffer has no data to return, eof in the response will be true. */ - async read(p: Uint8Array): Promise { + readSync(p: Uint8Array): ReadResult { if (this.empty()) { // Buffer is empty, reset to recover space. this.reset(); @@ -145,11 +145,21 @@ export class Buffer implements Reader, Writer { return { nread, eof: false }; } - async write(p: Uint8Array): Promise { + async read(p: Uint8Array): Promise { + const rr = this.readSync(p); + return Promise.resolve(rr); + } + + writeSync(p: Uint8Array): number { const m = this._grow(p.byteLength); return copyBytes(this.buf, p, m); } + async write(p: Uint8Array): Promise { + const n = this.writeSync(p); + return Promise.resolve(n); + } + /** _grow() grows the buffer to guarantee space for n more bytes. * It returns the index where bytes should be written. * If the buffer can't grow it will throw with ErrTooLarge. @@ -226,6 +236,27 @@ export class Buffer implements Reader, Writer { } } } + + /** Sync version of `readFrom` + */ + readFromSync(r: SyncReader): number { + let n = 0; + while (true) { + try { + const i = this._grow(MIN_READ); + this._reslice(i); + const fub = new Uint8Array(this.buf.buffer, i); + const { nread, eof } = r.readSync(fub); + this._reslice(i + nread); + n += nread; + if (eof) { + return n; + } + } catch (e) { + return n; + } + } + } } /** Read `r` until EOF and return the content as `Uint8Array`. @@ -235,3 +266,11 @@ export async function readAll(r: Reader): Promise { await buf.readFrom(r); return buf.bytes(); } + +/** Read synchronously `r` until EOF and return the content as `Uint8Array`. + */ +export function readAllSync(r: SyncReader): Uint8Array { + const buf = new Buffer(); + buf.readFromSync(r); + return buf.bytes(); +} diff --git a/js/buffer_test.ts b/js/buffer_test.ts index 90e171330b..f07d9d65d6 100644 --- a/js/buffer_test.ts +++ b/js/buffer_test.ts @@ -5,7 +5,7 @@ // https://github.com/golang/go/blob/master/LICENSE import { assertEquals, test } from "./test_util.ts"; -const { Buffer, readAll } = Deno; +const { Buffer, readAll, readAllSync } = Deno; type Buffer = Deno.Buffer; // N controls how many iterations of certain checks are performed. @@ -193,6 +193,23 @@ test(async function bufferReadFrom() { } }); +test(async function bufferReadFromSync() { + init(); + const buf = new Buffer(); + for (let i = 3; i < 30; i += 3) { + const s = await fillBytes( + buf, + "", + 5, + testBytes.subarray(0, Math.floor(testBytes.byteLength / i)) + ); + const b = new Buffer(); + b.readFromSync(buf); + const fub = new Uint8Array(testString.length); + await empty(b, s, fub); + } +}); + test(async function bufferTestGrow() { const tmp = new Uint8Array(72); for (let startLen of [0, 100, 1000, 10000, 100000]) { @@ -226,3 +243,13 @@ test(async function testReadAll() { assertEquals(testBytes[i], actualBytes[i]); } }); + +test(function testReadAllSync() { + init(); + const reader = new Buffer(testBytes.buffer as ArrayBuffer); + const actualBytes = readAllSync(reader); + assertEquals(testBytes.byteLength, actualBytes.byteLength); + for (let i = 0; i < testBytes.length; ++i) { + assertEquals(testBytes[i], actualBytes[i]); + } +}); diff --git a/js/deno.ts b/js/deno.ts index ed42dc89a4..f7505fea45 100644 --- a/js/deno.ts +++ b/js/deno.ts @@ -6,12 +6,16 @@ export { chdir, cwd } from "./dir"; export { File, open, + openSync, stdin, stdout, stderr, read, + readSync, write, + writeSync, seek, + seekSync, close, OpenMode } from "./files"; @@ -21,9 +25,12 @@ export { ReadResult, SeekMode, Reader, + SyncReader, Writer, + SyncWriter, Closer, Seeker, + SyncSeeker, ReadCloser, WriteCloser, ReadSeeker, @@ -31,7 +38,7 @@ export { ReadWriteCloser, ReadWriteSeeker } from "./io"; -export { Buffer, readAll } from "./buffer"; +export { Buffer, readAll, readAllSync } from "./buffer"; export { mkdirSync, mkdir } from "./mkdir"; export { makeTempDirSync, diff --git a/js/files.ts b/js/files.ts index 9ea3787356..9da9fbe0a7 100644 --- a/js/files.ts +++ b/js/files.ts @@ -1,10 +1,53 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import { Reader, Writer, Seeker, Closer, ReadResult, SeekMode } from "./io"; +import { + Reader, + Writer, + Seeker, + Closer, + ReadResult, + SeekMode, + SyncReader, + SyncWriter, + SyncSeeker +} from "./io"; import * as dispatch from "./dispatch"; import * as msg from "gen/msg_generated"; import { assert } from "./util"; import * as flatbuffers from "./flatbuffers"; +function reqOpen( + filename: string, + mode: OpenMode +): [flatbuffers.Builder, msg.Any, flatbuffers.Offset] { + const builder = flatbuffers.createBuilder(); + const filename_ = builder.createString(filename); + const mode_ = builder.createString(mode); + msg.Open.startOpen(builder); + msg.Open.addFilename(builder, filename_); + msg.Open.addMode(builder, mode_); + const inner = msg.Open.endOpen(builder); + return [builder, msg.Any.Open, inner]; +} + +function resOpen(baseRes: null | msg.Base): File { + assert(baseRes != null); + assert(msg.Any.OpenRes === baseRes!.innerType()); + const res = new msg.OpenRes(); + assert(baseRes!.inner(res) != null); + const rid = res.rid(); + // eslint-disable-next-line @typescript-eslint/no-use-before-define + return new File(rid); +} + +/** Open a file and return an instance of the `File` object + * synchronously. + * + * const file = Deno.openSync("/foo/bar.txt"); + */ +export function openSync(filename: string, mode: OpenMode = "r"): File { + return resOpen(dispatch.sendSync(...reqOpen(filename, mode))); +} + /** Open a file and return an instance of the `File` object. * * (async () => { @@ -15,33 +58,21 @@ export async function open( filename: string, mode: OpenMode = "r" ): Promise { - const builder = flatbuffers.createBuilder(); - const filename_ = builder.createString(filename); - const mode_ = builder.createString(mode); - msg.Open.startOpen(builder); - msg.Open.addFilename(builder, filename_); - msg.Open.addMode(builder, mode_); - const inner = msg.Open.endOpen(builder); - const baseRes = await dispatch.sendAsync(builder, msg.Any.Open, inner); - assert(baseRes != null); - assert(msg.Any.OpenRes === baseRes!.innerType()); - const res = new msg.OpenRes(); - assert(baseRes!.inner(res) != null); - const rid = res.rid(); - // eslint-disable-next-line @typescript-eslint/no-use-before-define - return new File(rid); + return resOpen(await dispatch.sendAsync(...reqOpen(filename, mode))); } -/** Read from a file ID into an array buffer. - * - * Resolves with the `ReadResult` for the operation. - */ -export async function read(rid: number, p: Uint8Array): Promise { +function reqRead( + rid: number, + p: Uint8Array +): [flatbuffers.Builder, msg.Any, flatbuffers.Offset, Uint8Array] { const builder = flatbuffers.createBuilder(); msg.Read.startRead(builder); msg.Read.addRid(builder, rid); const inner = msg.Read.endRead(builder); - const baseRes = await dispatch.sendAsync(builder, msg.Any.Read, inner, p); + return [builder, msg.Any.Read, inner, p]; +} + +function resRead(baseRes: null | msg.Base): ReadResult { assert(baseRes != null); assert(msg.Any.ReadRes === baseRes!.innerType()); const res = new msg.ReadRes(); @@ -49,16 +80,47 @@ export async function read(rid: number, p: Uint8Array): Promise { return { nread: res.nread(), eof: res.eof() }; } -/** Write to the file ID the contents of the array buffer. +/** Read synchronously from a file ID into an array buffer. + * + * Return `ReadResult` for the operation. + * + * const file = Deno.openSync("/foo/bar.txt"); + * const buf = new Uint8Array(100); + * const { nread, eof } = Deno.readSync(file.rid, buf); + * const text = new TextDecoder.decode(buf); * - * Resolves with the number of bytes written. */ -export async function write(rid: number, p: Uint8Array): Promise { +export function readSync(rid: number, p: Uint8Array): ReadResult { + return resRead(dispatch.sendSync(...reqRead(rid, p))); +} + +/** Read from a file ID into an array buffer. + * + * Resolves with the `ReadResult` for the operation. + * + * (async () => { + * const file = await Deno.open("/foo/bar.txt"); + * const buf = new Uint8Array(100); + * const { nread, eof } = await Deno.read(file.rid, buf); + * const text = new TextDecoder.decode(buf); + * })(); + */ +export async function read(rid: number, p: Uint8Array): Promise { + return resRead(await dispatch.sendAsync(...reqRead(rid, p))); +} + +function reqWrite( + rid: number, + p: Uint8Array +): [flatbuffers.Builder, msg.Any, flatbuffers.Offset, Uint8Array] { const builder = flatbuffers.createBuilder(); msg.Write.startWrite(builder); msg.Write.addRid(builder, rid); const inner = msg.Write.endWrite(builder); - const baseRes = await dispatch.sendAsync(builder, msg.Any.Write, inner, p); + return [builder, msg.Any.Write, inner, p]; +} + +function resWrite(baseRes: null | msg.Base): number { assert(baseRes != null); assert(msg.Any.WriteRes === baseRes!.innerType()); const res = new msg.WriteRes(); @@ -66,21 +128,71 @@ export async function write(rid: number, p: Uint8Array): Promise { return res.nbyte(); } -/** Seek a file ID to the given offset under mode given by `whence`. +/** Write synchronously to the file ID the contents of the array buffer. + * + * Resolves with the number of bytes written. + * + * const encoder = new TextEncoder(); + * const data = encoder.encode("Hello world\n"); + * const file = Deno.openSync("/foo/bar.txt"); + * Deno.writeSync(file.rid, data); + */ +export function writeSync(rid: number, p: Uint8Array): number { + return resWrite(dispatch.sendSync(...reqWrite(rid, p))); +} + +/** Write to the file ID the contents of the array buffer. + * + * Resolves with the number of bytes written. + * + * (async () => { + * const encoder = new TextEncoder(); + * const data = encoder.encode("Hello world\n"); + * const file = await Deno.open("/foo/bar.txt"); + * await Deno.write(file.rid, data); + * })(); * */ -export async function seek( +export async function write(rid: number, p: Uint8Array): Promise { + return resWrite(await dispatch.sendAsync(...reqWrite(rid, p))); +} + +function reqSeek( rid: number, offset: number, whence: SeekMode -): Promise { +): [flatbuffers.Builder, msg.Any, flatbuffers.Offset] { const builder = flatbuffers.createBuilder(); msg.Seek.startSeek(builder); msg.Seek.addRid(builder, rid); msg.Seek.addOffset(builder, offset); msg.Seek.addWhence(builder, whence); const inner = msg.Seek.endSeek(builder); - await dispatch.sendAsync(builder, msg.Any.Seek, inner); + return [builder, msg.Any.Seek, inner]; +} + +/** Seek a file ID synchronously to the given offset under mode given by `whence`. + * + * const file = Deno.openSync("/foo/bar.txt"); + * Deno.seekSync(file.rid, 0, 0); + */ +export function seekSync(rid: number, offset: number, whence: SeekMode): void { + dispatch.sendSync(...reqSeek(rid, offset, whence)); +} + +/** Seek a file ID to the given offset under mode given by `whence`. + * + * (async () => { + * const file = await Deno.open("/foo/bar.txt"); + * await Deno.seek(file.rid, 0, 0); + * })(); + */ +export async function seek( + rid: number, + offset: number, + whence: SeekMode +): Promise { + await dispatch.sendAsync(...reqSeek(rid, offset, whence)); } /** Close the file ID. */ @@ -93,21 +205,41 @@ export function close(rid: number): void { } /** The Deno abstraction for reading and writing files. */ -export class File implements Reader, Writer, Seeker, Closer { +export class File + implements + Reader, + SyncReader, + Writer, + SyncWriter, + Seeker, + SyncSeeker, + Closer { constructor(readonly rid: number) {} write(p: Uint8Array): Promise { return write(this.rid, p); } + writeSync(p: Uint8Array): number { + return writeSync(this.rid, p); + } + read(p: Uint8Array): Promise { return read(this.rid, p); } + readSync(p: Uint8Array): ReadResult { + return readSync(this.rid, p); + } + seek(offset: number, whence: SeekMode): Promise { return seek(this.rid, offset, whence); } + seekSync(offset: number, whence: SeekMode): void { + return seekSync(this.rid, offset, whence); + } + close(): void { close(this.rid); } diff --git a/js/files_test.ts b/js/files_test.ts index 0decd9f00b..f953946bc6 100644 --- a/js/files_test.ts +++ b/js/files_test.ts @@ -163,6 +163,19 @@ testPerm({ read: true }, async function seekStart() { assertEquals(decoded, "world!"); }); +testPerm({ read: true }, function seekSyncStart() { + const filename = "tests/hello.txt"; + const file = Deno.openSync(filename); + // Deliberately move 1 step forward + file.readSync(new Uint8Array(1)); // "H" + // Skipping "Hello " + file.seekSync(6, Deno.SeekMode.SEEK_START); + const buf = new Uint8Array(6); + file.readSync(buf); + const decoded = new TextDecoder().decode(buf); + assertEquals(decoded, "world!"); +}); + testPerm({ read: true }, async function seekCurrent() { const filename = "tests/hello.txt"; const file = await Deno.open(filename); @@ -176,6 +189,19 @@ testPerm({ read: true }, async function seekCurrent() { assertEquals(decoded, "world!"); }); +testPerm({ read: true }, function seekSyncCurrent() { + const filename = "tests/hello.txt"; + const file = Deno.openSync(filename); + // Deliberately move 1 step forward + file.readSync(new Uint8Array(1)); // "H" + // Skipping "ello " + file.seekSync(5, Deno.SeekMode.SEEK_CURRENT); + const buf = new Uint8Array(6); + file.readSync(buf); + const decoded = new TextDecoder().decode(buf); + assertEquals(decoded, "world!"); +}); + testPerm({ read: true }, async function seekEnd() { const filename = "tests/hello.txt"; const file = await Deno.open(filename); @@ -186,6 +212,16 @@ testPerm({ read: true }, async function seekEnd() { assertEquals(decoded, "world!"); }); +testPerm({ read: true }, function seekSyncEnd() { + const filename = "tests/hello.txt"; + const file = Deno.openSync(filename); + file.seekSync(-6, Deno.SeekMode.SEEK_END); + const buf = new Uint8Array(6); + file.readSync(buf); + const decoded = new TextDecoder().decode(buf); + assertEquals(decoded, "world!"); +}); + testPerm({ read: true }, async function seekMode() { const filename = "tests/hello.txt"; const file = await Deno.open(filename); diff --git a/js/io.ts b/js/io.ts index 7f56187c1a..dd3d12a6d5 100644 --- a/js/io.ts +++ b/js/io.ts @@ -49,6 +49,10 @@ export interface Reader { read(p: Uint8Array): Promise; } +export interface SyncReader { + readSync(p: Uint8Array): ReadResult; +} + // Writer is the interface that wraps the basic write() method. // https://golang.org/pkg/io/#Writer export interface Writer { @@ -63,6 +67,9 @@ export interface Writer { write(p: Uint8Array): Promise; } +export interface SyncWriter { + writeSync(p: Uint8Array): number; +} // https://golang.org/pkg/io/#Closer export interface Closer { // The behavior of Close after the first call is undefined. Specific @@ -85,6 +92,10 @@ export interface Seeker { seek(offset: number, whence: SeekMode): Promise; } +export interface SyncSeeker { + seekSync(offset: number, whence: SeekMode): void; +} + // https://golang.org/pkg/io/#ReadCloser export interface ReadCloser extends Reader, Closer {} diff --git a/js/read_file.ts b/js/read_file.ts index 3aeedec28c..1ca52e2769 100644 --- a/js/read_file.ts +++ b/js/read_file.ts @@ -1,29 +1,6 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import * as msg from "gen/msg_generated"; -import * as flatbuffers from "./flatbuffers"; -import { assert } from "./util"; -import * as dispatch from "./dispatch"; - -function req( - filename: string -): [flatbuffers.Builder, msg.Any, flatbuffers.Offset] { - const builder = flatbuffers.createBuilder(); - const filename_ = builder.createString(filename); - msg.ReadFile.startReadFile(builder); - msg.ReadFile.addFilename(builder, filename_); - const inner = msg.ReadFile.endReadFile(builder); - return [builder, msg.Any.ReadFile, inner]; -} - -function res(baseRes: null | msg.Base): Uint8Array { - assert(baseRes != null); - assert(msg.Any.ReadFileRes === baseRes!.innerType()); - const inner = new msg.ReadFileRes(); - assert(baseRes!.inner(inner) != null); - const dataArray = inner.dataArray(); - assert(dataArray != null); - return new Uint8Array(dataArray!); -} +import { open, openSync } from "./files"; +import { readAll, readAllSync } from "./buffer"; /** Read the entire contents of a file synchronously. * @@ -32,7 +9,10 @@ function res(baseRes: null | msg.Base): Uint8Array { * console.log(decoder.decode(data)); */ export function readFileSync(filename: string): Uint8Array { - return res(dispatch.sendSync(...req(filename))); + const file = openSync(filename); + const contents = readAllSync(file); + file.close(); + return contents; } /** Read the entire contents of a file. @@ -42,5 +22,8 @@ export function readFileSync(filename: string): Uint8Array { * console.log(decoder.decode(data)); */ export async function readFile(filename: string): Promise { - return res(await dispatch.sendAsync(...req(filename))); + const file = await open(filename); + const contents = await readAll(file); + file.close(); + return contents; } diff --git a/js/write_file.ts b/js/write_file.ts index 8c5c7ac0a4..39372a27e3 100644 --- a/js/write_file.ts +++ b/js/write_file.ts @@ -1,35 +1,7 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import * as msg from "gen/msg_generated"; -import * as flatbuffers from "./flatbuffers"; -import * as dispatch from "./dispatch"; - -function req( - filename: string, - data: Uint8Array, - options: WriteFileOptions -): [flatbuffers.Builder, msg.Any, flatbuffers.Offset, Uint8Array] { - const builder = flatbuffers.createBuilder(); - const filename_ = builder.createString(filename); - msg.WriteFile.startWriteFile(builder); - msg.WriteFile.addFilename(builder, filename_); - // Perm is not updated by default - if (options.perm !== undefined && options.perm !== null) { - msg.WriteFile.addUpdatePerm(builder, true); - msg.WriteFile.addPerm(builder, options.perm!); - } else { - msg.WriteFile.addUpdatePerm(builder, false); - msg.WriteFile.addPerm(builder, 0o666); - } - // Create is turned on by default - if (options.create !== undefined) { - msg.WriteFile.addIsCreate(builder, !!options.create); - } else { - msg.WriteFile.addIsCreate(builder, true); - } - msg.WriteFile.addIsAppend(builder, !!options.append); - const inner = msg.WriteFile.endWriteFile(builder); - return [builder, msg.Any.WriteFile, inner, data]; -} +import { stat, statSync } from "./stat"; +import { open, openSync } from "./files"; +import { chmod, chmodSync } from "./chmod"; /** Options for writing to a file. * `perm` would change the file's permission if set. @@ -53,7 +25,23 @@ export function writeFileSync( data: Uint8Array, options: WriteFileOptions = {} ): void { - dispatch.sendSync(...req(filename, data, options)); + if (options.create !== undefined) { + const create = !!options.create; + if (!create) { + // verify that file exists + statSync(filename); + } + } + + const openMode = !!options.append ? "a" : "w"; + const file = openSync(filename, openMode); + + if (options.perm !== undefined && options.perm !== null) { + chmodSync(filename, options.perm); + } + + file.writeSync(data); + file.close(); } /** Write a new file, with given filename and data. @@ -67,5 +55,21 @@ export async function writeFile( data: Uint8Array, options: WriteFileOptions = {} ): Promise { - await dispatch.sendAsync(...req(filename, data, options)); + if (options.create !== undefined) { + const create = !!options.create; + if (!create) { + // verify that file exists + await stat(filename); + } + } + + const openMode = !!options.append ? "a" : "w"; + const file = await open(filename, openMode); + + if (options.perm !== undefined && options.perm !== null) { + await chmod(filename, options.perm); + } + + await file.write(data); + file.close(); }