diff options
-rw-r--r-- | BUILD.gn | 2 | ||||
-rw-r--r-- | js/deno.ts | 4 | ||||
-rw-r--r-- | js/fileinfo.ts | 108 | ||||
-rw-r--r-- | js/read_dir.ts | 49 | ||||
-rw-r--r-- | js/read_dir_test.ts | 60 | ||||
-rw-r--r-- | js/stat.ts | 93 | ||||
-rw-r--r-- | js/unit_tests.ts | 1 | ||||
-rw-r--r-- | src/handlers.rs | 58 | ||||
-rw-r--r-- | src/msg.fbs | 12 |
9 files changed, 294 insertions, 93 deletions
@@ -75,6 +75,7 @@ ts_sources = [ "js/dom_types.ts", "js/errors.ts", "js/fetch.ts", + "js/fileinfo.ts", "js/files.ts", "js/io.ts", "js/global-eval.ts", @@ -88,6 +89,7 @@ ts_sources = [ "js/platform.ts", "js/plugins.d.ts", "js/read_file.ts", + "js/read_dir.ts", "js/remove.ts", "js/rename.ts", "js/read_link.ts", diff --git a/js/deno.ts b/js/deno.ts index 62816b8e1..b4181de14 100644 --- a/js/deno.ts +++ b/js/deno.ts @@ -9,9 +9,10 @@ export { makeTempDirSync, makeTempDir } from "./make_temp_dir"; export { removeSync, remove, removeAllSync, removeAll } from "./remove"; export { renameSync, rename } from "./rename"; export { readFileSync, readFile } from "./read_file"; +export { readDirSync, readDir } from "./read_dir"; export { copyFileSync, copyFile } from "./copy_file"; export { readlinkSync, readlink } from "./read_link"; -export { FileInfo, statSync, lstatSync, stat, lstat } from "./stat"; +export { statSync, lstatSync, stat, lstat } from "./stat"; export { symlinkSync, symlink } from "./symlink"; export { writeFileSync, writeFile } from "./write_file"; export { ErrorKind, DenoError } from "./errors"; @@ -19,4 +20,5 @@ export { libdeno } from "./libdeno"; export { platform } from "./platform"; export { trace } from "./trace"; export { truncateSync, truncate } from "./truncate"; +export { FileInfo } from "./fileinfo"; export const args: string[] = []; diff --git a/js/fileinfo.ts b/js/fileinfo.ts new file mode 100644 index 000000000..44668974e --- /dev/null +++ b/js/fileinfo.ts @@ -0,0 +1,108 @@ +// Copyright 2018 the Deno authors. All rights reserved. MIT license. +import * as fbs from "gen/msg_generated"; + +/** + * A FileInfo describes a file and is returned by `stat`, `lstat`, + * `statSync`, `lstatSync`. + */ +export interface FileInfo { + readonly _isFile: boolean; + readonly _isSymlink: boolean; + /** The size of the file, in bytes. */ + len: number; + /** + * The last modification time of the file. This corresponds to the `mtime` + * field from `stat` on Unix and `ftLastWriteTime` on Windows. This may not + * be available on all platforms. + */ + modified: number | null; + /** + * The last access time of the file. This corresponds to the `atime` + * field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not + * be available on all platforms. + */ + accessed: number | null; + /** + * The last access time of the file. This corresponds to the `birthtime` + * field from `stat` on Unix and `ftCreationTime` on Windows. This may not + * be available on all platforms. + */ + created: number | null; + /** + * The underlying raw st_mode bits that contain the standard Unix permissions + * for this file/directory. TODO Match behavior with Go on windows for mode. + */ + mode: number | null; + + /** + * Returns the file or directory name. + */ + name: string | null; + + /** Returns the file or directory path. */ + path: string | null; + + /** + * Returns whether this is info for a regular file. This result is mutually + * exclusive to `FileInfo.isDirectory` and `FileInfo.isSymlink`. + */ + isFile(): boolean; + + /** + * Returns whether this is info for a regular directory. This result is + * mutually exclusive to `FileInfo.isFile` and `FileInfo.isSymlink`. + */ + isDirectory(): boolean; + + /** + * Returns whether this is info for a symlink. This result is + * mutually exclusive to `FileInfo.isFile` and `FileInfo.isDirectory`. + */ + isSymlink(): boolean; +} + +export class FileInfoImpl implements FileInfo { + readonly _isFile: boolean; + readonly _isSymlink: boolean; + len: number; + modified: number | null; + accessed: number | null; + created: number | null; + mode: number | null; + name: string | null; + path: string | null; + + /* @internal */ + constructor(private _msg: fbs.StatRes) { + const modified = this._msg.modified().toFloat64(); + const accessed = this._msg.accessed().toFloat64(); + const created = this._msg.created().toFloat64(); + const hasMode = this._msg.hasMode(); + const mode = this._msg.mode(); // negative for invalid mode (Windows) + const name = this._msg.name(); + const path = this._msg.path(); + + this._isFile = this._msg.isFile(); + this._isSymlink = this._msg.isSymlink(); + this.len = this._msg.len().toFloat64(); + this.modified = modified ? modified : null; + this.accessed = accessed ? accessed : null; + this.created = created ? created : null; + // null on Windows + this.mode = hasMode ? mode : null; + this.name = name ? name : null; + this.path = path ? path : null; + } + + isFile() { + return this._isFile; + } + + isDirectory() { + return !this._isFile && !this._isSymlink; + } + + isSymlink() { + return this._isSymlink; + } +} diff --git a/js/read_dir.ts b/js/read_dir.ts new file mode 100644 index 000000000..822177aa8 --- /dev/null +++ b/js/read_dir.ts @@ -0,0 +1,49 @@ +// Copyright 2018 the Deno authors. All rights reserved. MIT license. +import * as fbs from "gen/msg_generated"; +import { flatbuffers } from "flatbuffers"; +import * as dispatch from "./dispatch"; +import { FileInfo, FileInfoImpl } from "./fileinfo"; +import { assert } from "./util"; + +/** + * Reads the directory given by path and returns + * a list of file info synchronously. + * + * import { readDirSync } from "deno"; + * const files = readDirSync("/"); + */ +export function readDirSync(path: string): FileInfo[] { + return res(dispatch.sendSync(...req(path))); +} + +/** + * Reads the directory given by path and returns a list of file info. + * + * import { readDir } from "deno"; + * const files = await readDir("/"); + * + */ +export async function readDir(path: string): Promise<FileInfo[]> { + return res(await dispatch.sendAsync(...req(path))); +} + +function req(path: string): [flatbuffers.Builder, fbs.Any, flatbuffers.Offset] { + const builder = new flatbuffers.Builder(); + const path_ = builder.createString(path); + fbs.ReadDir.startReadDir(builder); + fbs.ReadDir.addPath(builder, path_); + const msg = fbs.ReadDir.endReadDir(builder); + return [builder, fbs.Any.ReadDir, msg]; +} + +function res(baseRes: null | fbs.Base): FileInfo[] { + assert(baseRes != null); + assert(fbs.Any.ReadDirRes === baseRes!.msgType()); + const res = new fbs.ReadDirRes(); + assert(baseRes!.msg(res) != null); + const fileInfos: FileInfo[] = []; + for (let i = 0; i < res.entriesLength(); i++) { + fileInfos.push(new FileInfoImpl(res.entries(i)!)); + } + return fileInfos; +} diff --git a/js/read_dir_test.ts b/js/read_dir_test.ts new file mode 100644 index 000000000..43430ed00 --- /dev/null +++ b/js/read_dir_test.ts @@ -0,0 +1,60 @@ +// Copyright 2018 the Deno authors. All rights reserved. MIT license. +import { test, testPerm, assert, assertEqual } from "./test_util.ts"; +import * as deno from "deno"; +import { FileInfo } from "deno"; + +function assertSameContent(files: FileInfo[]) { + let counter = 0; + + for (const file of files) { + if (file.name == "subdir") { + assert(file.isDirectory()); + counter++; + } + + if (file.name === "002_hello.ts") { + assertEqual(file.path, `tests/${file.name}`); + counter++; + } + } + + assertEqual(counter, 2); +} + +testPerm({ write: true }, function readDirSyncSuccess() { + const files = deno.readDirSync("tests/"); + assertSameContent(files); +}); + +test(function readDirSyncNotDir() { + let caughtError = false; + let src; + + try { + src = deno.readDirSync("package.json"); + } catch (err) { + caughtError = true; + assertEqual(err.kind, deno.ErrorKind.Other); + } + assert(caughtError); + assertEqual(src, undefined); +}); + +test(function readDirSyncNotFound() { + let caughtError = false; + let src; + + try { + src = deno.readDirSync("bad_dir_name"); + } catch (err) { + caughtError = true; + assertEqual(err.kind, deno.ErrorKind.NotFound); + } + assert(caughtError); + assertEqual(src, undefined); +}); + +testPerm({ write: true }, async function readDirSuccess() { + const files = await deno.readDir("tests/"); + assertSameContent(files); +}); diff --git a/js/stat.ts b/js/stat.ts index b2b18c57c..284679f84 100644 --- a/js/stat.ts +++ b/js/stat.ts @@ -3,98 +3,7 @@ import * as fbs from "gen/msg_generated"; import { flatbuffers } from "flatbuffers"; import * as dispatch from "./dispatch"; import { assert } from "./util"; - -/** - * A FileInfo describes a file and is returned by `stat`, `lstat`, - * `statSync`, `lstatSync`. - */ -export interface FileInfo { - readonly _isFile: boolean; - readonly _isSymlink: boolean; - /** The size of the file, in bytes. */ - len: number; - /** - * The last modification time of the file. This corresponds to the `mtime` - * field from `stat` on Unix and `ftLastWriteTime` on Windows. This may not - * be available on all platforms. - */ - modified: number | null; - /** - * The last access time of the file. This corresponds to the `atime` - * field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not - * be available on all platforms. - */ - accessed: number | null; - /** - * The last access time of the file. This corresponds to the `birthtime` - * field from `stat` on Unix and `ftCreationTime` on Windows. This may not - * be available on all platforms. - */ - created: number | null; - /** - * The underlying raw st_mode bits that contain the standard Unix permissions - * for this file/directory. TODO Match behavior with Go on windows for mode. - */ - mode: number | null; - - /** - * Returns whether this is info for a regular file. This result is mutually - * exclusive to `FileInfo.isDirectory` and `FileInfo.isSymlink`. - */ - isFile(): boolean; - - /** - * Returns whether this is info for a regular directory. This result is - * mutually exclusive to `FileInfo.isFile` and `FileInfo.isSymlink`. - */ - isDirectory(): boolean; - - /** - * Returns whether this is info for a symlink. This result is - * mutually exclusive to `FileInfo.isFile` and `FileInfo.isDirectory`. - */ - isSymlink(): boolean; -} - -class FileInfoImpl implements FileInfo { - readonly _isFile: boolean; - readonly _isSymlink: boolean; - len: number; - modified: number | null; - accessed: number | null; - created: number | null; - mode: number | null; - - /* @internal */ - constructor(private _msg: fbs.StatRes) { - const modified = this._msg.modified().toFloat64(); - const accessed = this._msg.accessed().toFloat64(); - const created = this._msg.created().toFloat64(); - const hasMode = this._msg.hasMode(); - const mode = this._msg.mode(); // negative for invalid mode (Windows) - - this._isFile = this._msg.isFile(); - this._isSymlink = this._msg.isSymlink(); - this.len = this._msg.len().toFloat64(); - this.modified = modified ? modified : null; - this.accessed = accessed ? accessed : null; - this.created = created ? created : null; - // null on Windows - this.mode = hasMode ? mode : null; - } - - isFile() { - return this._isFile; - } - - isDirectory() { - return !this._isFile && !this._isSymlink; - } - - isSymlink() { - return this._isSymlink; - } -} +import { FileInfo, FileInfoImpl } from "./fileinfo"; /** * Queries the file system for information on the path provided. diff --git a/js/unit_tests.ts b/js/unit_tests.ts index e33fcf245..4ec5a720c 100644 --- a/js/unit_tests.ts +++ b/js/unit_tests.ts @@ -7,6 +7,7 @@ import "./fetch_test.ts"; import "./os_test.ts"; import "./files_test.ts"; import "./read_file_test.ts"; +import "./read_dir_test.ts"; import "./write_file_test.ts"; import "./copy_file_test.ts"; import "./mkdir_test.ts"; diff --git a/src/handlers.rs b/src/handlers.rs index c7e7ab7c5..80eb2871e 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -76,6 +76,7 @@ pub fn msg_from_js( msg::Any::Write => handle_write, msg::Any::Remove => handle_remove, msg::Any::ReadFile => handle_read_file, + msg::Any::ReadDir => handle_read_dir, msg::Any::Rename => handle_rename, msg::Any::Readlink => handle_read_link, msg::Any::Symlink => handle_symlink, @@ -816,6 +817,63 @@ fn handle_stat( }) } +fn handle_read_dir( + _state: Arc<IsolateState>, + base: &msg::Base, + data: &'static mut [u8], +) -> Box<Op> { + assert_eq!(data.len(), 0); + let msg = base.msg_as_read_dir().unwrap(); + let cmd_id = base.cmd_id(); + let path = String::from(msg.path().unwrap()); + + blocking!(base.sync(), || -> OpResult { + debug!("handle_read_dir {}", path); + let builder = &mut FlatBufferBuilder::new(); + let entries: Vec<_> = fs::read_dir(Path::new(&path))? + .map(|entry| { + let entry = entry.unwrap(); + let metadata = entry.metadata().unwrap(); + let file_type = metadata.file_type(); + let name = builder.create_string(entry.file_name().to_str().unwrap()); + let path = builder.create_string(entry.path().to_str().unwrap()); + + msg::StatRes::create( + builder, + &msg::StatResArgs { + is_file: file_type.is_file(), + is_symlink: file_type.is_symlink(), + len: metadata.len(), + modified: to_seconds!(metadata.modified()), + accessed: to_seconds!(metadata.accessed()), + created: to_seconds!(metadata.created()), + name: Some(name), + path: Some(path), + ..Default::default() + }, + ) + }).collect(); + + let entries = builder.create_vector(&entries); + let msg = msg::ReadDirRes::create( + builder, + &msg::ReadDirResArgs { + entries: Some(entries), + ..Default::default() + }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + msg: Some(msg.as_union_value()), + msg_type: msg::Any::ReadDirRes, + ..Default::default() + }, + )) + }) +} + fn handle_write_file( state: Arc<IsolateState>, base: &msg::Base, diff --git a/src/msg.fbs b/src/msg.fbs index e4886b6a9..83dca11e6 100644 --- a/src/msg.fbs +++ b/src/msg.fbs @@ -16,6 +16,8 @@ union Any { Remove, ReadFile, ReadFileRes, + ReadDir, + ReadDirRes, WriteFile, CopyFile, Rename, @@ -191,6 +193,14 @@ table ReadFileRes { data: [ubyte]; } +table ReadDir { + path: string; +} + +table ReadDirRes { + entries: [StatRes]; +} + table WriteFile { filename: string; data: [ubyte]; @@ -235,6 +245,8 @@ table StatRes { created:ulong; mode: uint; has_mode: bool; // false on windows + name: string; + path: string; } table Truncate { |