From c2b91a3ef6305bb0597ec65e887bbab2d9f512a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 12 Dec 2018 18:05:58 +0100 Subject: [PATCH] Flesh out open() modes (#1282) --- js/files.ts | 31 ++++++++++++++++-- js/files_test.ts | 83 +++++++++++++++++++++++++++++++++++++++++++++++- src/msg.fbs | 1 + src/ops.rs | 42 ++++++++++++++++++++++-- 4 files changed, 151 insertions(+), 6 deletions(-) diff --git a/js/files.ts b/js/files.ts index ef00375112..4392eadf7b 100644 --- a/js/files.ts +++ b/js/files.ts @@ -29,14 +29,37 @@ export const stdout = new File(1); /** An instance of `File` for stderr. */ export const stderr = new File(2); -// TODO This is just a placeholder - not final API. -export type OpenMode = "r" | "w" | "w+" | "x"; +type OpenMode = + /** Read-only. Default. Starts at beginning of file. */ + | "r" + /** Read-write. Start at beginning of file. */ + | "r+" + /** Write-only. Opens and truncates existing file or creates new one for + * writing only. + */ + | "w" + /** Read-write. Opens and truncates existing file or creates new one for + * writing and reading. + */ + | "w+" + /** Write-only. Opens existing file or creates new one. Each write appends + * content to the end of file. + */ + | "a" + /** Read-write. Behaves like "a" and allows to read from file. */ + | "a+" + /** Write-only. Exclusive create - creates new file only if one doesn't exist + * already. + */ + | "x" + /** Read-write. Behaves like `x` and allows to read from file. */ + | "x+"; /** A factory function for creating instances of `File` associated with the * supplied file name. */ export function create(filename: string): Promise { - return open(filename, "x"); + return open(filename, "w+"); } /** Open a file and return an instance of the `File` object. @@ -52,8 +75,10 @@ export async function open( ): 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); diff --git a/js/files_test.ts b/js/files_test.ts index 37f63d9fa2..d46a469060 100644 --- a/js/files_test.ts +++ b/js/files_test.ts @@ -1,6 +1,6 @@ // Copyright 2018 the Deno authors. All rights reserved. MIT license. import * as deno from "deno"; -import { test, assert, assertEqual } from "./test_util.ts"; +import { test, testPerm, assert, assertEqual } from "./test_util.ts"; test(function filesStdioFileDescriptors() { assertEqual(deno.stdin.rid, 0); @@ -29,3 +29,84 @@ test(async function filesToAsyncIterator() { assertEqual(totalSize, 12); }); + +testPerm({ write: true }, async function createFile() { + const tempDir = await deno.makeTempDir(); + const filename = tempDir + "/test.txt"; + let f = await deno.open(filename, "w"); + let fileInfo = deno.statSync(filename); + assert(fileInfo.isFile()); + assert(fileInfo.len === 0); + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + await f.write(data); + fileInfo = deno.statSync(filename); + assert(fileInfo.len === 5); + f.close(); + + // TODO: test different modes + await deno.removeAll(tempDir); +}); + +testPerm({ write: true }, async function openModeWrite() { + const tempDir = deno.makeTempDirSync(); + const encoder = new TextEncoder(); + const filename = tempDir + "hello.txt"; + const data = encoder.encode("Hello world!\n"); + + let file = await deno.open(filename, "w"); + // assert file was created + let fileInfo = deno.statSync(filename); + assert(fileInfo.isFile()); + assertEqual(fileInfo.len, 0); + // write some data + await file.write(data); + fileInfo = deno.statSync(filename); + assertEqual(fileInfo.len, 13); + // assert we can't read from file + let thrown = false; + try { + const buf = new Uint8Array(20); + await file.read(buf); + } catch (e) { + thrown = true; + } finally { + assert(thrown, "'w' mode shouldn't allow to read file"); + } + file.close(); + // assert that existing file is truncated on open + file = await deno.open(filename, "w"); + file.close(); + const fileSize = deno.statSync(filename).len; + assertEqual(fileSize, 0); + await deno.removeAll(tempDir); +}); + +testPerm({ write: true }, async function openModeWriteRead() { + const tempDir = deno.makeTempDirSync(); + const encoder = new TextEncoder(); + const filename = tempDir + "hello.txt"; + const data = encoder.encode("Hello world!\n"); + + let file = await deno.open(filename, "w+"); + // assert file was created + let fileInfo = deno.statSync(filename); + assert(fileInfo.isFile()); + assertEqual(fileInfo.len, 0); + // write some data + await file.write(data); + fileInfo = deno.statSync(filename); + assertEqual(fileInfo.len, 13); + + // TODO: this test is not working, I expect because + // file handle points to the end of file, but ATM + // deno has no seek implementation on Rust side + // assert file can be read + // const buf = new Uint8Array(20); + // const result = await file.read(buf); + // console.log(result.eof, result.nread); + // assertEqual(result.nread, 13); + // file.close(); + + await deno.removeAll(tempDir); +}); diff --git a/src/msg.fbs b/src/msg.fbs index dcec126dd9..9cdb628a15 100644 --- a/src/msg.fbs +++ b/src/msg.fbs @@ -348,6 +348,7 @@ table Truncate { table Open { filename: string; perm: uint; + mode: string; } table OpenRes { diff --git a/src/ops.rs b/src/ops.rs index 6d41fbe639..3cf466d0d5 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -584,9 +584,47 @@ fn op_open( let cmd_id = base.cmd_id(); let inner = base.inner_as_open().unwrap(); let filename = PathBuf::from(inner.filename().unwrap()); - // TODO let perm = inner.perm(); + // let perm = inner.perm(); + let mode = inner.mode().unwrap(); - let op = tokio::fs::File::open(filename) + let mut open_options = tokio::fs::OpenOptions::new(); + + match mode { + "r" => { + open_options.read(true); + } + "r+" => { + open_options.read(true).write(true); + } + "w" => { + open_options.create(true).write(true).truncate(true); + } + "w+" => { + open_options + .read(true) + .create(true) + .write(true) + .truncate(true); + } + "a" => { + open_options.create(true).append(true); + } + "a+" => { + open_options.read(true).create(true).append(true); + } + "x" => { + open_options.create_new(true).write(true); + } + "x+" => { + open_options.create_new(true).read(true).write(true); + } + &_ => { + panic!("Unknown file open mode."); + } + } + + let op = open_options + .open(filename) .map_err(DenoError::from) .and_then(move |fs_file| -> OpResult { let resource = resources::add_fs_file(fs_file);