diff --git a/Cargo.lock b/Cargo.lock index fe1743063b..c0fa63f143 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,6 +348,7 @@ dependencies = [ "dlopen", "dprint-plugin-typescript", "encoding_rs", + "filetime", "futures", "fwdansi", "http", @@ -564,9 +565,9 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "filetime" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695" +checksum = "3ed85775dcc68644b5c950ac06a2b23768d3bc9390464151aaf27136998dcf9e" dependencies = [ "cfg-if", "libc", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index e0f75da15b..7c8414e55d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -42,6 +42,7 @@ dlopen = "0.1.8" encoding_rs = "0.8.23" dprint-plugin-typescript = "0.30.2" futures = "0.3.5" +filetime = "0.2.12" http = "0.2.1" idna = "0.2.0" indexmap = "1.5.1" diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index 14079f3001..9c5590f977 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -1247,6 +1247,40 @@ declare namespace Deno { export function createHttpClient( options: CreateHttpClientOptions, ): HttpClient; + + /** **UNSTABLE**: needs investigation into high precision time. + * + * Synchronously changes the access (`atime`) and modification (`mtime`) times + * of a file stream resource referenced by `rid`. Given times are either in + * seconds (UNIX epoch time) or as `Date` objects. + * + * ```ts + * const file = Deno.openSync("file.txt", { create: true }); + * Deno.futimeSync(file.rid, 1556495550, new Date()); + * ``` + */ + export function futimeSync( + rid: number, + atime: number | Date, + mtime: number | Date, + ): void; + + /** **UNSTABLE**: needs investigation into high precision time. + * + * Changes the access (`atime`) and modification (`mtime`) times of a file + * stream resource referenced by `rid`. Given times are either in seconds + * (UNIX epoch time) or as `Date` objects. + * + * ```ts + * const file = await Deno.open("file.txt", { create: true }); + * await Deno.futime(file.rid, 1556495550, new Date()); + * ``` + */ + export function futime( + rid: number, + atime: number | Date, + mtime: number | Date, + ): Promise; } declare function fetch( diff --git a/cli/ops/fs.rs b/cli/ops/fs.rs index 3b8946057d..794518e2cb 100644 --- a/cli/ops/fs.rs +++ b/cli/ops/fs.rs @@ -174,6 +174,12 @@ pub fn init(i: &mut CoreIsolate, s: &Rc) { i.register_op("op_cwd", s.stateful_json_op_sync(t, op_cwd)); + i.register_op("op_futime_sync", s.stateful_json_op_sync(t, op_futime_sync)); + i.register_op( + "op_futime_async", + s.stateful_json_op_async(t, op_futime_async), + ); + i.register_op("op_utime_sync", s.stateful_json_op_sync(t, op_utime_sync)); i.register_op( "op_utime_async", @@ -1673,6 +1679,65 @@ async fn op_make_temp_file_async( .unwrap() } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct FutimeArgs { + rid: i32, + atime: i64, + mtime: i64, +} + +fn op_futime_sync( + state: &State, + resource_table: &mut ResourceTable, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + state.check_unstable("Deno.futimeSync"); + let args: FutimeArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + let atime = filetime::FileTime::from_unix_time(args.atime, 0); + let mtime = filetime::FileTime::from_unix_time(args.mtime, 0); + + std_file_resource(resource_table, rid, |r| match r { + Ok(std_file) => { + filetime::set_file_handle_times(std_file, Some(atime), Some(mtime)) + .map_err(ErrBox::from) + } + Err(_) => Err(ErrBox::type_error( + "cannot futime on this type of resource".to_string(), + )), + })?; + + Ok(json!({})) +} + +async fn op_futime_async( + state: Rc, + resource_table: Rc>, + args: Value, + _zero_copy: BufVec, +) -> Result { + state.check_unstable("Deno.futime"); + let args: FutimeArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + let atime = filetime::FileTime::from_unix_time(args.atime, 0); + let mtime = filetime::FileTime::from_unix_time(args.mtime, 0); + + let mut resource_table = resource_table.borrow_mut(); + std_file_resource(&mut resource_table, rid, |r| match r { + Ok(std_file) => { + filetime::set_file_handle_times(std_file, Some(atime), Some(mtime)) + .map_err(ErrBox::from) + } + Err(_) => Err(ErrBox::type_error( + "cannot futime on this type of resource".to_string(), + )), + })?; + + Ok(json!({})) +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct UtimeArgs { diff --git a/cli/rt/30_fs.js b/cli/rt/30_fs.js index d8171bdaca..750c3c1ba1 100644 --- a/cli/rt/30_fs.js +++ b/cli/rt/30_fs.js @@ -268,6 +268,32 @@ return v instanceof Date ? Math.trunc(v.valueOf() / 1000) : v; } + function futimeSync( + rid, + atime, + mtime, + ) { + sendSync("op_futime_sync", { + rid, + // TODO(caspervonb) split atime, mtime into [seconds, nanoseconds] tuple + atime: toSecondsFromEpoch(atime), + mtime: toSecondsFromEpoch(mtime), + }); + } + + async function futime( + rid, + atime, + mtime, + ) { + await sendAsync("op_futime_async", { + rid, + // TODO(caspervonb) split atime, mtime into [seconds, nanoseconds] tuple + atime: toSecondsFromEpoch(atime), + mtime: toSecondsFromEpoch(mtime), + }); + } + function utimeSync( path, atime, @@ -364,6 +390,8 @@ umask, link, linkSync, + futime, + futimeSync, utime, utimeSync, symlink, diff --git a/cli/rt/90_deno_ns.js b/cli/rt/90_deno_ns.js index ac22410f62..82acdef3c3 100644 --- a/cli/rt/90_deno_ns.js +++ b/cli/rt/90_deno_ns.js @@ -118,6 +118,8 @@ __bootstrap.denoNsUnstable = { umask: __bootstrap.fs.umask, link: __bootstrap.fs.link, linkSync: __bootstrap.fs.linkSync, + futime: __bootstrap.fs.futime, + futimeSync: __bootstrap.fs.futimeSync, utime: __bootstrap.fs.utime, utimeSync: __bootstrap.fs.utimeSync, symlink: __bootstrap.fs.symlink, diff --git a/cli/tests/unit/utime_test.ts b/cli/tests/unit/utime_test.ts index cc68e90cd5..185187ef4d 100644 --- a/cli/tests/unit/utime_test.ts +++ b/cli/tests/unit/utime_test.ts @@ -13,6 +13,50 @@ function assertFuzzyTimestampEquals(t1: Date | null, t2: Date): void { assert(Math.abs(t1.valueOf() - t2.valueOf()) < 10_000); } +unitTest( + { perms: { read: true, write: true } }, + async function futimeSyncSuccess(): Promise { + const testDir = await Deno.makeTempDir(); + const filename = testDir + "/file.txt"; + const file = await Deno.open(filename, { + create: true, + write: true, + }); + + const atime = 1000; + const mtime = 50000; + await Deno.futime(file.rid, atime, mtime); + await Deno.fdatasync(file.rid); + + const fileInfo = Deno.statSync(filename); + assertFuzzyTimestampEquals(fileInfo.atime, new Date(atime * 1000)); + assertFuzzyTimestampEquals(fileInfo.mtime, new Date(mtime * 1000)); + file.close(); + }, +); + +unitTest( + { perms: { read: true, write: true } }, + function futimeSyncSuccess(): void { + const testDir = Deno.makeTempDirSync(); + const filename = testDir + "/file.txt"; + const file = Deno.openSync(filename, { + create: true, + write: true, + }); + + const atime = 1000; + const mtime = 50000; + Deno.futimeSync(file.rid, atime, mtime); + Deno.fdatasyncSync(file.rid); + + const fileInfo = Deno.statSync(filename); + assertFuzzyTimestampEquals(fileInfo.atime, new Date(atime * 1000)); + assertFuzzyTimestampEquals(fileInfo.mtime, new Date(mtime * 1000)); + file.close(); + }, +); + unitTest( { perms: { read: true, write: true } }, function utimeSyncFileSuccess(): void { diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js index 79af46e31f..aa334907a4 100644 --- a/cli/tsc/99_main_compiler.js +++ b/cli/tsc/99_main_compiler.js @@ -86,6 +86,8 @@ delete Object.prototype.__proto__; "fdatasync", "fdatasyncSync", "formatDiagnostics", + "futime", + "futimeSync", "fstat", "fstatSync", "fsync",