diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2023-03-07 05:13:44 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-07 18:13:44 +0900 |
commit | fe368b72c1dd2f2d17b7b7e5965b9e3d9ca61c35 (patch) | |
tree | ebc76fb86e3bee1baf1e336feb36a507affa7316 /ext/fs/30_fs.js | |
parent | 64354f41125642d420d80cbf617dfb8ddca398e5 (diff) |
refactor: Add "deno_fs" extension crate (#18040)
This commit factors out APIs related to file system from "runtime/"
to a separate "deno_fs" extension crate.
Diffstat (limited to 'ext/fs/30_fs.js')
-rw-r--r-- | ext/fs/30_fs.js | 899 |
1 files changed, 899 insertions, 0 deletions
diff --git a/ext/fs/30_fs.js b/ext/fs/30_fs.js new file mode 100644 index 000000000..19e7f372b --- /dev/null +++ b/ext/fs/30_fs.js @@ -0,0 +1,899 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeFilter, + Date, + DatePrototype, + Error, + Function, + MathTrunc, + ObjectEntries, + ObjectPrototypeIsPrototypeOf, + ObjectValues, + SymbolAsyncIterator, + SymbolIterator, + Uint32Array, +} = primordials; +import { read, readSync, write, writeSync } from "internal:deno_io/12_io.js"; +import * as abortSignal from "internal:deno_web/03_abort_signal.js"; +import { + readableStreamForRid, + ReadableStreamPrototype, + writableStreamForRid, +} from "internal:deno_web/06_streams.js"; +import { pathFromURL } from "internal:deno_web/00_infra.js"; + +function chmodSync(path, mode) { + ops.op_chmod_sync(pathFromURL(path), mode); +} + +async function chmod(path, mode) { + await core.opAsync("op_chmod_async", pathFromURL(path), mode); +} + +function chownSync( + path, + uid, + gid, +) { + ops.op_chown_sync(pathFromURL(path), uid, gid); +} + +async function chown( + path, + uid, + gid, +) { + await core.opAsync( + "op_chown_async", + pathFromURL(path), + uid, + gid, + ); +} + +function copyFileSync( + fromPath, + toPath, +) { + ops.op_copy_file_sync( + pathFromURL(fromPath), + pathFromURL(toPath), + ); +} + +async function copyFile( + fromPath, + toPath, +) { + await core.opAsync( + "op_copy_file_async", + pathFromURL(fromPath), + pathFromURL(toPath), + ); +} + +function cwd() { + return ops.op_cwd(); +} + +function chdir(directory) { + ops.op_chdir(pathFromURL(directory)); +} + +function makeTempDirSync(options = {}) { + return ops.op_make_temp_dir_sync(options); +} + +function makeTempDir(options = {}) { + return core.opAsync("op_make_temp_dir_async", options); +} + +function makeTempFileSync(options = {}) { + return ops.op_make_temp_file_sync(options); +} + +function makeTempFile(options = {}) { + return core.opAsync("op_make_temp_file_async", options); +} + +function mkdirArgs(path, options) { + const args = { path: pathFromURL(path), recursive: false }; + if (options != null) { + if (typeof options.recursive == "boolean") { + args.recursive = options.recursive; + } + if (options.mode) { + args.mode = options.mode; + } + } + return args; +} + +function mkdirSync(path, options) { + ops.op_mkdir_sync(mkdirArgs(path, options)); +} + +async function mkdir( + path, + options, +) { + await core.opAsync("op_mkdir_async", mkdirArgs(path, options)); +} + +function readDirSync(path) { + return ops.op_read_dir_sync(pathFromURL(path))[ + SymbolIterator + ](); +} + +function readDir(path) { + const array = core.opAsync( + "op_read_dir_async", + pathFromURL(path), + ); + return { + async *[SymbolAsyncIterator]() { + yield* await array; + }, + }; +} + +function readLinkSync(path) { + return ops.op_read_link_sync(pathFromURL(path)); +} + +function readLink(path) { + return core.opAsync("op_read_link_async", pathFromURL(path)); +} + +function realPathSync(path) { + return ops.op_realpath_sync(pathFromURL(path)); +} + +function realPath(path) { + return core.opAsync("op_realpath_async", pathFromURL(path)); +} + +function removeSync( + path, + options = {}, +) { + ops.op_remove_sync( + pathFromURL(path), + !!options.recursive, + ); +} + +async function remove( + path, + options = {}, +) { + await core.opAsync( + "op_remove_async", + pathFromURL(path), + !!options.recursive, + ); +} + +function renameSync(oldpath, newpath) { + ops.op_rename_sync( + pathFromURL(oldpath), + pathFromURL(newpath), + ); +} + +async function rename(oldpath, newpath) { + await core.opAsync( + "op_rename_async", + pathFromURL(oldpath), + pathFromURL(newpath), + ); +} + +// Extract the FsStat object from the encoded buffer. +// See `runtime/ops/fs.rs` for the encoder. +// +// This is not a general purpose decoder. There are 4 types: +// +// 1. date +// offset += 4 +// 1/0 | extra padding | high u32 | low u32 +// if date[0] == 1, new Date(u64) else null +// +// 2. bool +// offset += 2 +// 1/0 | extra padding +// +// 3. u64 +// offset += 2 +// high u32 | low u32 +// +// 4. ?u64 converts a zero u64 value to JS null on Windows. +function createByteStruct(types) { + // types can be "date", "bool" or "u64". + // `?` prefix means optional on windows. + let offset = 0; + let str = + 'const unix = Deno.build.os === "darwin" || Deno.build.os === "linux"; return {'; + const typeEntries = ObjectEntries(types); + for (let i = 0; i < typeEntries.length; ++i) { + let { 0: name, 1: type } = typeEntries[i]; + + const optional = type.startsWith("?"); + if (optional) type = type.slice(1); + + if (type == "u64") { + if (!optional) { + str += `${name}: view[${offset}] + view[${offset + 1}] * 2**32,`; + } else { + str += `${name}: (unix ? (view[${offset}] + view[${ + offset + 1 + }] * 2**32) : (view[${offset}] + view[${ + offset + 1 + }] * 2**32) || null),`; + } + } else if (type == "date") { + str += `${name}: view[${offset}] === 0 ? null : new Date(view[${ + offset + 2 + }] + view[${offset + 3}] * 2**32),`; + offset += 2; + } else { + str += `${name}: !!(view[${offset}] + view[${offset + 1}] * 2**32),`; + } + offset += 2; + } + str += "};"; + // ...so you don't like eval huh? don't worry, it only executes during snapshot :) + return [new Function("view", str), new Uint32Array(offset)]; +} + +const { 0: statStruct, 1: statBuf } = createByteStruct({ + isFile: "bool", + isDirectory: "bool", + isSymlink: "bool", + size: "u64", + mtime: "date", + atime: "date", + birthtime: "date", + dev: "?u64", + ino: "?u64", + mode: "?u64", + nlink: "?u64", + uid: "?u64", + gid: "?u64", + rdev: "?u64", + blksize: "?u64", + blocks: "?u64", +}); + +function parseFileInfo(response) { + const unix = core.build.os === "darwin" || core.build.os === "linux"; + return { + isFile: response.isFile, + isDirectory: response.isDirectory, + isSymlink: response.isSymlink, + size: response.size, + mtime: response.mtimeSet !== null ? new Date(response.mtime) : null, + atime: response.atimeSet !== null ? new Date(response.atime) : null, + birthtime: response.birthtimeSet !== null + ? new Date(response.birthtime) + : null, + // Only non-null if on Unix + dev: unix ? response.dev : null, + ino: unix ? response.ino : null, + mode: unix ? response.mode : null, + nlink: unix ? response.nlink : null, + uid: unix ? response.uid : null, + gid: unix ? response.gid : null, + rdev: unix ? response.rdev : null, + blksize: unix ? response.blksize : null, + blocks: unix ? response.blocks : null, + }; +} + +function fstatSync(rid) { + ops.op_fstat_sync(rid, statBuf); + return statStruct(statBuf); +} + +async function fstat(rid) { + return parseFileInfo(await core.opAsync("op_fstat_async", rid)); +} + +async function lstat(path) { + const res = await core.opAsync("op_stat_async", { + path: pathFromURL(path), + lstat: true, + }); + return parseFileInfo(res); +} + +function lstatSync(path) { + ops.op_stat_sync( + pathFromURL(path), + true, + statBuf, + ); + return statStruct(statBuf); +} + +async function stat(path) { + const res = await core.opAsync("op_stat_async", { + path: pathFromURL(path), + lstat: false, + }); + return parseFileInfo(res); +} + +function statSync(path) { + ops.op_stat_sync( + pathFromURL(path), + false, + statBuf, + ); + return statStruct(statBuf); +} + +function coerceLen(len) { + if (len == null || len < 0) { + return 0; + } + + return len; +} + +function ftruncateSync(rid, len) { + ops.op_ftruncate_sync(rid, coerceLen(len)); +} + +async function ftruncate(rid, len) { + await core.opAsync("op_ftruncate_async", rid, coerceLen(len)); +} + +function truncateSync(path, len) { + ops.op_truncate_sync(path, coerceLen(len)); +} + +async function truncate(path, len) { + await core.opAsync("op_truncate_async", path, coerceLen(len)); +} + +function umask(mask) { + return ops.op_umask(mask); +} + +function linkSync(oldpath, newpath) { + ops.op_link_sync(oldpath, newpath); +} + +async function link(oldpath, newpath) { + await core.opAsync("op_link_async", oldpath, newpath); +} + +function toUnixTimeFromEpoch(value) { + if (ObjectPrototypeIsPrototypeOf(DatePrototype, value)) { + const time = value.valueOf(); + const seconds = MathTrunc(time / 1e3); + const nanoseconds = MathTrunc(time - (seconds * 1e3)) * 1e6; + + return [ + seconds, + nanoseconds, + ]; + } + + const seconds = value; + const nanoseconds = 0; + + return [ + seconds, + nanoseconds, + ]; +} + +function futimeSync( + rid, + atime, + mtime, +) { + const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); + const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); + ops.op_futime_sync(rid, atimeSec, atimeNsec, mtimeSec, mtimeNsec); +} + +async function futime( + rid, + atime, + mtime, +) { + const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); + const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); + await core.opAsync( + "op_futime_async", + rid, + atimeSec, + atimeNsec, + mtimeSec, + mtimeNsec, + ); +} + +function utimeSync( + path, + atime, + mtime, +) { + const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); + const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); + ops.op_utime_sync( + pathFromURL(path), + atimeSec, + atimeNsec, + mtimeSec, + mtimeNsec, + ); +} + +async function utime( + path, + atime, + mtime, +) { + const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); + const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); + await core.opAsync( + "op_utime_async", + pathFromURL(path), + atimeSec, + atimeNsec, + mtimeSec, + mtimeNsec, + ); +} + +function symlinkSync( + oldpath, + newpath, + options, +) { + ops.op_symlink_sync( + pathFromURL(oldpath), + pathFromURL(newpath), + options?.type, + ); +} + +async function symlink( + oldpath, + newpath, + options, +) { + await core.opAsync( + "op_symlink_async", + pathFromURL(oldpath), + pathFromURL(newpath), + options?.type, + ); +} + +function fdatasyncSync(rid) { + ops.op_fdatasync_sync(rid); +} + +async function fdatasync(rid) { + await core.opAsync("op_fdatasync_async", rid); +} + +function fsyncSync(rid) { + ops.op_fsync_sync(rid); +} + +async function fsync(rid) { + await core.opAsync("op_fsync_async", rid); +} + +function flockSync(rid, exclusive) { + ops.op_flock_sync(rid, exclusive === true); +} + +async function flock(rid, exclusive) { + await core.opAsync("op_flock_async", rid, exclusive === true); +} + +function funlockSync(rid) { + ops.op_funlock_sync(rid); +} + +async function funlock(rid) { + await core.opAsync("op_funlock_async", rid); +} + +function seekSync( + rid, + offset, + whence, +) { + return ops.op_seek_sync({ rid, offset, whence }); +} + +function seek( + rid, + offset, + whence, +) { + return core.opAsync("op_seek_async", { rid, offset, whence }); +} + +function openSync( + path, + options, +) { + if (options) checkOpenOptions(options); + const mode = options?.mode; + const rid = ops.op_open_sync( + pathFromURL(path), + options, + mode, + ); + + return new FsFile(rid); +} + +async function open( + path, + options, +) { + if (options) checkOpenOptions(options); + const mode = options?.mode; + const rid = await core.opAsync( + "op_open_async", + pathFromURL(path), + options, + mode, + ); + + return new FsFile(rid); +} + +function createSync(path) { + return openSync(path, { + read: true, + write: true, + truncate: true, + create: true, + }); +} + +function create(path) { + return open(path, { + read: true, + write: true, + truncate: true, + create: true, + }); +} + +class FsFile { + #rid = 0; + + #readable; + #writable; + + constructor(rid) { + this.#rid = rid; + } + + get rid() { + return this.#rid; + } + + write(p) { + return write(this.rid, p); + } + + writeSync(p) { + return writeSync(this.rid, p); + } + + truncate(len) { + return ftruncate(this.rid, len); + } + + truncateSync(len) { + return ftruncateSync(this.rid, len); + } + + read(p) { + return read(this.rid, p); + } + + readSync(p) { + return readSync(this.rid, p); + } + + seek(offset, whence) { + return seek(this.rid, offset, whence); + } + + seekSync(offset, whence) { + return seekSync(this.rid, offset, whence); + } + + stat() { + return fstat(this.rid); + } + + statSync() { + return fstatSync(this.rid); + } + + close() { + core.close(this.rid); + } + + get readable() { + if (this.#readable === undefined) { + this.#readable = readableStreamForRid(this.rid); + } + return this.#readable; + } + + get writable() { + if (this.#writable === undefined) { + this.#writable = writableStreamForRid(this.rid); + } + return this.#writable; + } +} + +function checkOpenOptions(options) { + if ( + ArrayPrototypeFilter( + ObjectValues(options), + (val) => val === true, + ).length === 0 + ) { + throw new Error("OpenOptions requires at least one option to be true"); + } + + if (options.truncate && !options.write) { + throw new Error("'truncate' option requires 'write' option"); + } + + const createOrCreateNewWithoutWriteOrAppend = + (options.create || options.createNew) && + !(options.write || options.append); + + if (createOrCreateNewWithoutWriteOrAppend) { + throw new Error( + "'create' or 'createNew' options require 'write' or 'append' option", + ); + } +} + +const File = FsFile; + +function readFileSync(path) { + return ops.op_readfile_sync(pathFromURL(path)); +} + +async function readFile(path, options) { + let cancelRid; + let abortHandler; + if (options?.signal) { + options.signal.throwIfAborted(); + cancelRid = ops.op_cancel_handle(); + abortHandler = () => core.tryClose(cancelRid); + options.signal[abortSignal.add](abortHandler); + } + + try { + const read = await core.opAsync( + "op_readfile_async", + pathFromURL(path), + cancelRid, + ); + return read; + } finally { + if (options?.signal) { + options.signal[abortSignal.remove](abortHandler); + + // always throw the abort error when aborted + options.signal.throwIfAborted(); + } + } +} + +function readTextFileSync(path) { + return ops.op_readfile_text_sync(pathFromURL(path)); +} + +async function readTextFile(path, options) { + let cancelRid; + let abortHandler; + if (options?.signal) { + options.signal.throwIfAborted(); + cancelRid = ops.op_cancel_handle(); + abortHandler = () => core.tryClose(cancelRid); + options.signal[abortSignal.add](abortHandler); + } + + try { + const read = await core.opAsync( + "op_readfile_text_async", + pathFromURL(path), + cancelRid, + ); + return read; + } finally { + if (options?.signal) { + options.signal[abortSignal.remove](abortHandler); + + // always throw the abort error when aborted + options.signal.throwIfAborted(); + } + } +} + +function writeFileSync( + path, + data, + options = {}, +) { + options.signal?.throwIfAborted(); + ops.op_write_file_sync( + pathFromURL(path), + options.mode, + options.append ?? false, + options.create ?? true, + options.createNew ?? false, + data, + ); +} + +async function writeFile( + path, + data, + options = {}, +) { + let cancelRid; + let abortHandler; + if (options.signal) { + options.signal.throwIfAborted(); + cancelRid = ops.op_cancel_handle(); + abortHandler = () => core.tryClose(cancelRid); + options.signal[abortSignal.add](abortHandler); + } + try { + if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) { + const file = await open(path, { + mode: options.mode, + append: options.append ?? false, + create: options.create ?? true, + createNew: options.createNew ?? false, + write: true, + }); + await data.pipeTo(file.writable, { + signal: options.signal, + }); + } else { + await core.opAsync( + "op_write_file_async", + pathFromURL(path), + options.mode, + options.append ?? false, + options.create ?? true, + options.createNew ?? false, + data, + cancelRid, + ); + } + } finally { + if (options.signal) { + options.signal[abortSignal.remove](abortHandler); + + // always throw the abort error when aborted + options.signal.throwIfAborted(); + } + } +} + +function writeTextFileSync( + path, + data, + options = {}, +) { + const encoder = new TextEncoder(); + return writeFileSync(path, encoder.encode(data), options); +} + +function writeTextFile( + path, + data, + options = {}, +) { + if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) { + return writeFile( + path, + data.pipeThrough(new TextEncoderStream()), + options, + ); + } else { + const encoder = new TextEncoder(); + return writeFile(path, encoder.encode(data), options); + } +} + +export { + chdir, + chmod, + chmodSync, + chown, + chownSync, + copyFile, + copyFileSync, + create, + createSync, + cwd, + fdatasync, + fdatasyncSync, + File, + flock, + flockSync, + FsFile, + fstat, + fstatSync, + fsync, + fsyncSync, + ftruncate, + ftruncateSync, + funlock, + funlockSync, + futime, + futimeSync, + link, + linkSync, + lstat, + lstatSync, + makeTempDir, + makeTempDirSync, + makeTempFile, + makeTempFileSync, + mkdir, + mkdirSync, + open, + openSync, + readDir, + readDirSync, + readFile, + readFileSync, + readLink, + readLinkSync, + readTextFile, + readTextFileSync, + realPath, + realPathSync, + remove, + removeSync, + rename, + renameSync, + seek, + seekSync, + stat, + statSync, + symlink, + symlinkSync, + truncate, + truncateSync, + umask, + utime, + utimeSync, + writeFile, + writeFileSync, + writeTextFile, + writeTextFileSync, +}; |