1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 15:24:46 -05:00

Rename FileInfo time fields and represent them as Date objects (#4932)

This patch also increases the resolution of reported file times to
sub-millisecond precision.
This commit is contained in:
Bert Belder 2020-04-27 20:09:56 +02:00
parent c190a0dbc4
commit ee4e6a1ef9
No known key found for this signature in database
GPG key ID: 7A77887B2E2ED461
9 changed files with 143 additions and 130 deletions

View file

@ -1316,15 +1316,15 @@ declare namespace Deno {
/** The last modification time of the file. This corresponds to the `mtime` /** The last modification time of the file. This corresponds to the `mtime`
* field from `stat` on Linux/Mac OS and `ftLastWriteTime` on Windows. This * field from `stat` on Linux/Mac OS and `ftLastWriteTime` on Windows. This
* may not be available on all platforms. */ * may not be available on all platforms. */
modified: number | null; mtime: Date | null;
/** The last access time of the file. This corresponds to the `atime` /** The last access time of the file. This corresponds to the `atime`
* field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not * field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not
* be available on all platforms. */ * be available on all platforms. */
accessed: number | null; atime: Date | null;
/** The creation time of the file. This corresponds to the `birthtime` /** The creation time of the file. This corresponds to the `birthtime`
* field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may not * field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may
* be available on all platforms. */ * not be available on all platforms. */
created: number | null; birthtime: Date | null;
/** ID of the device containing the file. /** ID of the device containing the file.
* *
* _Linux/Mac OS only._ */ * _Linux/Mac OS only._ */

View file

@ -4,9 +4,9 @@ import { build } from "../../build.ts";
export interface FileInfo { export interface FileInfo {
size: number; size: number;
modified: number | null; mtime: Date | null;
accessed: number | null; atime: Date | null;
created: number | null; birthtime: Date | null;
dev: number | null; dev: number | null;
ino: number | null; ino: number | null;
mode: number | null; mode: number | null;
@ -26,9 +26,9 @@ export interface StatResponse {
isDirectory: boolean; isDirectory: boolean;
isSymlink: boolean; isSymlink: boolean;
size: number; size: number;
modified: number; mtime: number | null;
accessed: number; atime: number | null;
created: number; birthtime: number | null;
// Null for stat(), but exists for readdir(). // Null for stat(), but exists for readdir().
name: string | null; name: string | null;
// Unix only members // Unix only members
@ -51,9 +51,9 @@ export function parseFileInfo(response: StatResponse): FileInfo {
isDirectory: response.isDirectory, isDirectory: response.isDirectory,
isSymlink: response.isSymlink, isSymlink: response.isSymlink,
size: response.size, size: response.size,
modified: response.modified ? response.modified : null, mtime: response.mtime != null ? new Date(response.mtime) : null,
accessed: response.accessed ? response.accessed : null, atime: response.atime != null ? new Date(response.atime) : null,
created: response.created ? response.created : null, birthtime: response.birthtime != null ? new Date(response.birthtime) : null,
// Only non-null if on Unix // Only non-null if on Unix
dev: isUnix ? response.dev : null, dev: isUnix ? response.dev : null,
ino: isUnix ? response.ino : null, ino: isUnix ? response.ino : null,

View file

@ -1,9 +1,9 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { unitTest, assert, assertEquals } from "./test_util.ts"; import { unitTest, assert, assertEquals } from "./test_util.ts";
// TODO Add tests for modified, accessed, and created fields once there is a way unitTest(
// to create temp files. { perms: { read: true, write: true } },
unitTest({ perms: { read: true } }, function statSyncSuccess(): void { function statSyncSuccess(): void {
const packageInfo = Deno.statSync("README.md"); const packageInfo = Deno.statSync("README.md");
assert(packageInfo.isFile); assert(packageInfo.isFile);
assert(!packageInfo.isSymlink); assert(!packageInfo.isSymlink);
@ -15,7 +15,17 @@ unitTest({ perms: { read: true } }, function statSyncSuccess(): void {
const testsInfo = Deno.statSync("cli/tests"); const testsInfo = Deno.statSync("cli/tests");
assert(testsInfo.isDirectory); assert(testsInfo.isDirectory);
assert(!testsInfo.isSymlink); assert(!testsInfo.isSymlink);
});
const tempFile = Deno.makeTempFileSync();
const tempInfo = Deno.statSync(tempFile);
const now = Date.now();
assert(tempInfo.atime !== null && now - tempInfo.atime.valueOf() < 1000);
assert(tempInfo.mtime !== null && now - tempInfo.mtime.valueOf() < 1000);
assert(
tempInfo.birthtime === null || now - tempInfo.birthtime.valueOf() < 1000
);
}
);
unitTest({ perms: { read: false } }, function statSyncPerm(): void { unitTest({ perms: { read: false } }, function statSyncPerm(): void {
let caughtError = false; let caughtError = false;
@ -83,9 +93,9 @@ unitTest({ perms: { read: true } }, function lstatSyncNotFound(): void {
assertEquals(badInfo, undefined); assertEquals(badInfo, undefined);
}); });
unitTest({ perms: { read: true } }, async function statSuccess(): Promise< unitTest(
void { perms: { read: true, write: true } },
> { async function statSuccess(): Promise<void> {
const packageInfo = await Deno.stat("README.md"); const packageInfo = await Deno.stat("README.md");
assert(packageInfo.isFile); assert(packageInfo.isFile);
assert(!packageInfo.isSymlink); assert(!packageInfo.isSymlink);
@ -97,7 +107,18 @@ unitTest({ perms: { read: true } }, async function statSuccess(): Promise<
const testsInfo = await Deno.stat("cli/tests"); const testsInfo = await Deno.stat("cli/tests");
assert(testsInfo.isDirectory); assert(testsInfo.isDirectory);
assert(!testsInfo.isSymlink); assert(!testsInfo.isSymlink);
});
const tempFile = await Deno.makeTempFile();
const tempInfo = await Deno.stat(tempFile);
const now = Date.now();
assert(tempInfo.atime !== null && now - tempInfo.atime.valueOf() < 1000);
assert(tempInfo.mtime !== null && now - tempInfo.mtime.valueOf() < 1000);
assert(
tempInfo.birthtime === null || now - tempInfo.birthtime.valueOf() < 1000
);
}
);
unitTest({ perms: { read: false } }, async function statPerm(): Promise<void> { unitTest({ perms: { read: false } }, async function statPerm(): Promise<void> {
let caughtError = false; let caughtError = false;

View file

@ -4,9 +4,9 @@ import { unitTest, assert } from "./test_util.ts";
// Allow 10 second difference. // Allow 10 second difference.
// Note this might not be enough for FAT (but we are not testing on such fs). // Note this might not be enough for FAT (but we are not testing on such fs).
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
function assertFuzzyTimestampEquals(t1: any, t2: number): void { function assertFuzzyTimestampEquals(t1: Date | null, t2: Date): void {
assert(typeof t1 === "number"); assert(t1 instanceof Date);
assert(Math.abs(t1 - t2) < 10); assert(Math.abs(t1.valueOf() - t2.valueOf()) < 10_000);
} }
unitTest( unitTest(
@ -23,8 +23,8 @@ unitTest(
Deno.utimeSync(filename, atime, mtime); Deno.utimeSync(filename, atime, mtime);
const fileInfo = Deno.statSync(filename); const fileInfo = Deno.statSync(filename);
assertFuzzyTimestampEquals(fileInfo.accessed, atime); assertFuzzyTimestampEquals(fileInfo.atime, new Date(atime * 1000));
assertFuzzyTimestampEquals(fileInfo.modified, mtime); assertFuzzyTimestampEquals(fileInfo.mtime, new Date(mtime * 1000));
} }
); );
@ -38,8 +38,8 @@ unitTest(
Deno.utimeSync(testDir, atime, mtime); Deno.utimeSync(testDir, atime, mtime);
const dirInfo = Deno.statSync(testDir); const dirInfo = Deno.statSync(testDir);
assertFuzzyTimestampEquals(dirInfo.accessed, atime); assertFuzzyTimestampEquals(dirInfo.atime, new Date(atime * 1000));
assertFuzzyTimestampEquals(dirInfo.modified, mtime); assertFuzzyTimestampEquals(dirInfo.mtime, new Date(mtime * 1000));
} }
); );
@ -48,13 +48,13 @@ unitTest(
function utimeSyncDateSuccess(): void { function utimeSyncDateSuccess(): void {
const testDir = Deno.makeTempDirSync(); const testDir = Deno.makeTempDirSync();
const atime = 1000; const atime = new Date(1000_000);
const mtime = 50000; const mtime = new Date(50000_000);
Deno.utimeSync(testDir, new Date(atime * 1000), new Date(mtime * 1000)); Deno.utimeSync(testDir, atime, mtime);
const dirInfo = Deno.statSync(testDir); const dirInfo = Deno.statSync(testDir);
assertFuzzyTimestampEquals(dirInfo.accessed, atime); assertFuzzyTimestampEquals(dirInfo.atime, atime);
assertFuzzyTimestampEquals(dirInfo.modified, mtime); assertFuzzyTimestampEquals(dirInfo.mtime, mtime);
} }
); );
@ -71,15 +71,8 @@ unitTest(
Deno.utimeSync(filename, atime, mtime); Deno.utimeSync(filename, atime, mtime);
const fileInfo = Deno.statSync(filename); const fileInfo = Deno.statSync(filename);
// atime and mtime must be scaled by a factor of 1000 to be recorded in seconds assertFuzzyTimestampEquals(fileInfo.atime, atime);
assertFuzzyTimestampEquals( assertFuzzyTimestampEquals(fileInfo.mtime, mtime);
fileInfo.accessed,
Math.trunc(atime.valueOf() / 1000)
);
assertFuzzyTimestampEquals(
fileInfo.modified,
Math.trunc(mtime.valueOf() / 1000)
);
} }
); );
@ -95,8 +88,8 @@ unitTest(
Deno.utimeSync(testDir, atime, mtime); Deno.utimeSync(testDir, atime, mtime);
const dirInfo = Deno.statSync(testDir); const dirInfo = Deno.statSync(testDir);
assertFuzzyTimestampEquals(dirInfo.accessed, atime); assertFuzzyTimestampEquals(dirInfo.atime, new Date(atime * 1000));
assertFuzzyTimestampEquals(dirInfo.modified, mtime); assertFuzzyTimestampEquals(dirInfo.mtime, new Date(mtime * 1000));
} }
); );
@ -148,8 +141,8 @@ unitTest(
await Deno.utime(filename, atime, mtime); await Deno.utime(filename, atime, mtime);
const fileInfo = Deno.statSync(filename); const fileInfo = Deno.statSync(filename);
assertFuzzyTimestampEquals(fileInfo.accessed, atime); assertFuzzyTimestampEquals(fileInfo.atime, new Date(atime * 1000));
assertFuzzyTimestampEquals(fileInfo.modified, mtime); assertFuzzyTimestampEquals(fileInfo.mtime, new Date(mtime * 1000));
} }
); );
@ -163,8 +156,8 @@ unitTest(
await Deno.utime(testDir, atime, mtime); await Deno.utime(testDir, atime, mtime);
const dirInfo = Deno.statSync(testDir); const dirInfo = Deno.statSync(testDir);
assertFuzzyTimestampEquals(dirInfo.accessed, atime); assertFuzzyTimestampEquals(dirInfo.atime, new Date(atime * 1000));
assertFuzzyTimestampEquals(dirInfo.modified, mtime); assertFuzzyTimestampEquals(dirInfo.mtime, new Date(mtime * 1000));
} }
); );
@ -173,13 +166,13 @@ unitTest(
async function utimeDateSuccess(): Promise<void> { async function utimeDateSuccess(): Promise<void> {
const testDir = Deno.makeTempDirSync(); const testDir = Deno.makeTempDirSync();
const atime = 1000; const atime = new Date(100_000);
const mtime = 50000; const mtime = new Date(5000_000);
await Deno.utime(testDir, new Date(atime * 1000), new Date(mtime * 1000)); await Deno.utime(testDir, atime, mtime);
const dirInfo = Deno.statSync(testDir); const dirInfo = Deno.statSync(testDir);
assertFuzzyTimestampEquals(dirInfo.accessed, atime); assertFuzzyTimestampEquals(dirInfo.atime, atime);
assertFuzzyTimestampEquals(dirInfo.modified, mtime); assertFuzzyTimestampEquals(dirInfo.mtime, mtime);
} }
); );
@ -197,15 +190,8 @@ unitTest(
await Deno.utime(filename, atime, mtime); await Deno.utime(filename, atime, mtime);
const fileInfo = Deno.statSync(filename); const fileInfo = Deno.statSync(filename);
// The dates must be scaled by a factored of 1000 to make them seconds assertFuzzyTimestampEquals(fileInfo.atime, atime);
assertFuzzyTimestampEquals( assertFuzzyTimestampEquals(fileInfo.mtime, mtime);
fileInfo.accessed,
Math.trunc(atime.valueOf() / 1000)
);
assertFuzzyTimestampEquals(
fileInfo.modified,
Math.trunc(mtime.valueOf() / 1000)
);
} }
); );

View file

@ -12,7 +12,9 @@ use deno_core::ZeroCopyBuf;
use futures::future::FutureExt; use futures::future::FutureExt;
use std::convert::From; use std::convert::From;
use std::env::{current_dir, set_current_dir, temp_dir}; use std::env::{current_dir, set_current_dir, temp_dir};
use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::SystemTime;
use std::time::UNIX_EPOCH; use std::time::UNIX_EPOCH;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
@ -441,14 +443,19 @@ fn op_copy_file(
}) })
} }
macro_rules! to_seconds { fn to_msec(maybe_time: Result<SystemTime, io::Error>) -> serde_json::Value {
($time:expr) => {{ match maybe_time {
// Unwrap is safe here as if the file is before the unix epoch Ok(time) => {
// something is very wrong. let msec = time
$time .duration_since(UNIX_EPOCH)
.and_then(|t| Ok(t.duration_since(UNIX_EPOCH).unwrap().as_secs())) .map(|t| t.as_secs_f64() * 1000f64)
.unwrap_or(0) .unwrap_or_else(|err| err.duration().as_secs_f64() * -1000f64);
}}; serde_json::Number::from_f64(msec)
.map(serde_json::Value::Number)
.unwrap_or(serde_json::Value::Null)
}
Err(_) => serde_json::Value::Null,
}
} }
#[inline(always)] #[inline(always)]
@ -477,10 +484,10 @@ fn get_stat_json(
"isDirectory": metadata.is_dir(), "isDirectory": metadata.is_dir(),
"isSymlink": metadata.file_type().is_symlink(), "isSymlink": metadata.file_type().is_symlink(),
"size": metadata.len(), "size": metadata.len(),
// In seconds. Available on both Unix or Windows. // In milliseconds, like JavaScript. Available on both Unix or Windows.
"modified":to_seconds!(metadata.modified()), "mtime": to_msec(metadata.modified()),
"accessed":to_seconds!(metadata.accessed()), "atime": to_msec(metadata.accessed()),
"created":to_seconds!(metadata.created()), "birthtime": to_msec(metadata.created()),
// Following are only valid under Unix. // Following are only valid under Unix.
"dev": usm!(dev), "dev": usm!(dev),
"ino": usm!(ino), "ino": usm!(ino),

View file

@ -317,10 +317,9 @@ export class Tar {
const mode = const mode =
opts.fileMode || (info && info.mode) || parseInt("777", 8) & 0xfff, opts.fileMode || (info && info.mode) || parseInt("777", 8) & 0xfff,
mtime = mtime = Math.floor(
opts.mtime || opts.mtime ?? (info?.mtime ?? new Date()).valueOf() / 1000
(info && info.modified) || ),
Math.floor(new Date().getTime() / 1000),
uid = opts.uid || 0, uid = opts.uid || 0,
gid = opts.gid || 0; gid = opts.gid || 0;
if (typeof opts.owner === "string" && opts.owner.length >= 32) { if (typeof opts.owner === "string" && opts.owner.length >= 32) {

View file

@ -85,9 +85,9 @@ async function copyFile(
await Deno.copyFile(src, dest); await Deno.copyFile(src, dest);
if (options.preserveTimestamps) { if (options.preserveTimestamps) {
const statInfo = await Deno.stat(src); const statInfo = await Deno.stat(src);
assert(statInfo.accessed != null, `statInfo.accessed is unavailable`); assert(statInfo.atime instanceof Date, `statInfo.atime is unavailable`);
assert(statInfo.modified != null, `statInfo.modified is unavailable`); assert(statInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
await Deno.utime(dest, statInfo.accessed, statInfo.modified); await Deno.utime(dest, statInfo.atime, statInfo.mtime);
} }
} }
/* copy file to dest synchronously */ /* copy file to dest synchronously */
@ -96,9 +96,9 @@ function copyFileSync(src: string, dest: string, options: CopyOptions): void {
Deno.copyFileSync(src, dest); Deno.copyFileSync(src, dest);
if (options.preserveTimestamps) { if (options.preserveTimestamps) {
const statInfo = Deno.statSync(src); const statInfo = Deno.statSync(src);
assert(statInfo.accessed != null, `statInfo.accessed is unavailable`); assert(statInfo.atime instanceof Date, `statInfo.atime is unavailable`);
assert(statInfo.modified != null, `statInfo.modified is unavailable`); assert(statInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
Deno.utimeSync(dest, statInfo.accessed, statInfo.modified); Deno.utimeSync(dest, statInfo.atime, statInfo.mtime);
} }
} }
@ -114,9 +114,9 @@ async function copySymLink(
await Deno.symlink(originSrcFilePath, dest, type); await Deno.symlink(originSrcFilePath, dest, type);
if (options.preserveTimestamps) { if (options.preserveTimestamps) {
const statInfo = await Deno.lstat(src); const statInfo = await Deno.lstat(src);
assert(statInfo.accessed != null, `statInfo.accessed is unavailable`); assert(statInfo.atime instanceof Date, `statInfo.atime is unavailable`);
assert(statInfo.modified != null, `statInfo.modified is unavailable`); assert(statInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
await Deno.utime(dest, statInfo.accessed, statInfo.modified); await Deno.utime(dest, statInfo.atime, statInfo.mtime);
} }
} }
@ -132,9 +132,9 @@ function copySymlinkSync(
Deno.symlinkSync(originSrcFilePath, dest, type); Deno.symlinkSync(originSrcFilePath, dest, type);
if (options.preserveTimestamps) { if (options.preserveTimestamps) {
const statInfo = Deno.lstatSync(src); const statInfo = Deno.lstatSync(src);
assert(statInfo.accessed != null, `statInfo.accessed is unavailable`); assert(statInfo.atime instanceof Date, `statInfo.atime is unavailable`);
assert(statInfo.modified != null, `statInfo.modified is unavailable`); assert(statInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
Deno.utimeSync(dest, statInfo.accessed, statInfo.modified); Deno.utimeSync(dest, statInfo.atime, statInfo.mtime);
} }
} }
@ -152,9 +152,9 @@ async function copyDir(
if (options.preserveTimestamps) { if (options.preserveTimestamps) {
const srcStatInfo = await Deno.stat(src); const srcStatInfo = await Deno.stat(src);
assert(srcStatInfo.accessed != null, `statInfo.accessed is unavailable`); assert(srcStatInfo.atime instanceof Date, `statInfo.atime is unavailable`);
assert(srcStatInfo.modified != null, `statInfo.modified is unavailable`); assert(srcStatInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
await Deno.utime(dest, srcStatInfo.accessed, srcStatInfo.modified); await Deno.utime(dest, srcStatInfo.atime, srcStatInfo.mtime);
} }
for await (const file of Deno.readdir(src)) { for await (const file of Deno.readdir(src)) {
@ -180,9 +180,9 @@ function copyDirSync(src: string, dest: string, options: CopyOptions): void {
if (options.preserveTimestamps) { if (options.preserveTimestamps) {
const srcStatInfo = Deno.statSync(src); const srcStatInfo = Deno.statSync(src);
assert(srcStatInfo.accessed != null, `statInfo.accessed is unavailable`); assert(srcStatInfo.atime instanceof Date, `statInfo.atime is unavailable`);
assert(srcStatInfo.modified != null, `statInfo.modified is unavailable`); assert(srcStatInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
Deno.utimeSync(dest, srcStatInfo.accessed, srcStatInfo.modified); Deno.utimeSync(dest, srcStatInfo.atime, srcStatInfo.mtime);
} }
for (const file of Deno.readdirSync(src)) { for (const file of Deno.readdirSync(src)) {

View file

@ -143,8 +143,8 @@ testCopy(
const srcStatInfo = await Deno.stat(srcFile); const srcStatInfo = await Deno.stat(srcFile);
assert(typeof srcStatInfo.accessed === "number"); assert(srcStatInfo.atime instanceof Date);
assert(typeof srcStatInfo.modified === "number"); assert(srcStatInfo.mtime instanceof Date);
// Copy with overwrite and preserve timestamps options. // Copy with overwrite and preserve timestamps options.
await copy(srcFile, destFile, { await copy(srcFile, destFile, {
@ -154,10 +154,10 @@ testCopy(
const destStatInfo = await Deno.stat(destFile); const destStatInfo = await Deno.stat(destFile);
assert(typeof destStatInfo.accessed === "number"); assert(destStatInfo.atime instanceof Date);
assert(typeof destStatInfo.modified === "number"); assert(destStatInfo.mtime instanceof Date);
assertEquals(destStatInfo.accessed, srcStatInfo.accessed); assertEquals(destStatInfo.atime, srcStatInfo.atime);
assertEquals(destStatInfo.modified, srcStatInfo.modified); assertEquals(destStatInfo.mtime, srcStatInfo.mtime);
} }
); );
@ -327,8 +327,8 @@ testCopySync(
const srcStatInfo = Deno.statSync(srcFile); const srcStatInfo = Deno.statSync(srcFile);
assert(typeof srcStatInfo.accessed === "number"); assert(srcStatInfo.atime instanceof Date);
assert(typeof srcStatInfo.modified === "number"); assert(srcStatInfo.mtime instanceof Date);
// Copy with overwrite and preserve timestamps options. // Copy with overwrite and preserve timestamps options.
copySync(srcFile, destFile, { copySync(srcFile, destFile, {
@ -338,12 +338,12 @@ testCopySync(
const destStatInfo = Deno.statSync(destFile); const destStatInfo = Deno.statSync(destFile);
assert(typeof destStatInfo.accessed === "number"); assert(destStatInfo.atime instanceof Date);
assert(typeof destStatInfo.modified === "number"); assert(destStatInfo.mtime instanceof Date);
// TODO: Activate test when https://github.com/denoland/deno/issues/2411 // TODO: Activate test when https://github.com/denoland/deno/issues/2411
// is fixed // is fixed
// assertEquals(destStatInfo.accessed, srcStatInfo.accessed); // assertEquals(destStatInfo.atime, srcStatInfo.atime);
// assertEquals(destStatInfo.modified, srcStatInfo.modified); // assertEquals(destStatInfo.mtime, srcStatInfo.mtime);
} }
); );

View file

@ -7,9 +7,9 @@ class DirEntryMock implements Deno.DirEntry {
isDirectory = false; isDirectory = false;
isSymlink = false; isSymlink = false;
size = -1; size = -1;
modified = -1; mtime = new Date(-1);
accessed = -1; atime = new Date(-1);
created = -1; birthtime = new Date(-1);
name = ""; name = "";
dev = -1; dev = -1;
ino = -1; ino = -1;