From 6acea8dec1d77e6ea3125a78d53705dffb8fa7da Mon Sep 17 00:00:00 2001 From: Bailey Herbert Date: Wed, 16 Mar 2022 14:20:47 -0700 Subject: [PATCH] Add synchronous functions --- README.md | 13 +++++++ cpp/binding.cc | 29 +++++++++++++++ src/main.ts | 91 +++++++++++++++++++++++++++++++++++++++++++++- tests/main.test.ts | 16 ++++++++ tests/utils.ts | 49 ++++++++++++++++++++++++- 5 files changed, 195 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8efdc05..febbf25 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,17 @@ utimes('/path/to/file', 447775200000, function(error) { }); ``` +### Working synchronously + +This package also offers synchronous versions of its functions. + +```ts +import { utimesSync, lutimesSync } from 'utimes'; + +utimesSync('/path/to/file', 447775200000); +lutimesSync('/path/to/symlink', 447775200000); +``` + ### Errors Starting with version 5.0.0, this package throws descriptive and user-friendly error messages. Please note that these messages come from the operating system and will not be consistent between systems. Here's an example: @@ -94,3 +105,5 @@ This was originally a fork of [@ronomon/utimes](https://www.npmjs.com/package/@r - Supports changing timestamps for symbolic links (with [`lutimes`](#symbolic-links)) - Throws descriptive errors instead of numbers - Modern API with both promises and callbacks written in TypeScript + +Huge thanks to all of the [contributors](https://github.com/baileyherbert/utimes/graphs/contributors) who helped with maintaining and improving this package! diff --git a/cpp/binding.cc b/cpp/binding.cc index 271138c..5fc5066 100644 --- a/cpp/binding.cc +++ b/cpp/binding.cc @@ -231,12 +231,41 @@ void utimes(const Napi::CallbackInfo& info) { worker->Queue(); } +void utimesSync(const Napi::CallbackInfo& info) { + if (info.Length() != 6 || !info[0].IsBuffer() || !info[1].IsNumber() || !info[2].IsNumber() || !info[3].IsNumber() || !info[4].IsNumber() || !info[5].IsBoolean()) { + throw Napi::Error::New(info.Env(), "bad arguments, expected: (" + "buffer path, int flags, " + "seconds btime, seconds mtime, seconds atime, bool resolveLinks" + ")" + ); + } + + Napi::Buffer pathHandle = info[0].As>(); + const uint8_t flags = info[1].As().Uint32Value(); + const uint64_t btime = info[2].As().Int64Value(); + const uint64_t mtime = info[3].As().Int64Value(); + const uint64_t atime = info[4].As().Int64Value(); + const bool resolveLinks = info[5].As().Value(); + + try { + set_utimes(pathHandle.Data(), flags, btime, mtime, atime, resolveLinks); + } + catch (std::string error) { + throw Napi::Error::New(info.Env(), error); + } +} + Napi::Object Init(Napi::Env env, Napi::Object exports) { exports.Set( Napi::String::New(env, "utimes"), Napi::Function::New(env) ); + exports.Set( + Napi::String::New(env, "utimesSync"), + Napi::Function::New(env) + ); + return exports; } diff --git a/src/main.ts b/src/main.ts index 9f0ec5f..df5ea25 100644 --- a/src/main.ts +++ b/src/main.ts @@ -39,6 +39,16 @@ export function utimes(path: Paths, options: TimeOptions, callback?: Callback) { return invokeWrapped(path, options, true, callback); } +/** + * Synchronously updates the timestamps on the given path(s). + * + * @param path + * @param options + */ +export function utimesSync(path: Paths, options: TimeOptions): void { + return invokeUTimesSync(path, options, true); +} + /** * Updates the timestamps on the given path(s). If the path(s) point to a symbolic link, then the timestamps of @@ -57,6 +67,16 @@ export function lutimes(path: Paths, options: TimeOptions, callback?: Callback) return invokeWrapped(path, options, false, callback); } +/** + * Synchronously updates the timestamps on the given path(s). + * + * @param path + * @param options + */ +export function lutimesSync(path: Paths, options: TimeOptions): void { + return invokeUTimesSync(path, options, false); +} + /** * Invokes utimes with the given options, and implements callbacks/promises based on the parameters. * @@ -107,7 +127,7 @@ function invokeUTimes(path: Paths, options: TimeOptions, resolveLinks: boolean, // Invoke the native addon on supported platforms if (useNativeAddon) { - invokeBinding( + invokeBindingAsync( target, times, flags, @@ -140,6 +160,55 @@ function invokeUTimes(path: Paths, options: TimeOptions, resolveLinks: boolean, invokeAtIndex(0); } +/** + * Invokes utimes synchronously with the given options. + * + * @param path A string path or an array of string paths. + * @param options The timestamps to use. + * @param resolveLinks Whether or not to resolve symbolic links and update their target file instead. + * @returns + */ +function invokeUTimesSync(path: Paths, options: TimeOptions, resolveLinks: boolean) { + const targets = getNormalizedPaths(path); + const times = getNormalizedOptions(options); + const flags = getFlags(times); + + const invokeAtIndex = (index: number) => { + const target = targets[index]; + + if (target === undefined) { + return; + } + + // Invoke the native addon on supported platforms + if (useNativeAddon) { + invokeBindingSync(target, times, flags, resolveLinks); + invokeAtIndex(index + 1); + } + + // Fall back to using `fs.utimes` for other platforms + else { + const stats = fs[resolveLinks ? 'statSync' : 'lstatSync'](target); + + fs[resolveLinks ? 'utimesSync' : 'lutimesSync']( + target, + (flags & 4 ? times.atime : stats.atime.getTime()) / 1000, + (flags & 2 ? times.mtime : stats.mtime.getTime()) / 1000 + ); + + invokeAtIndex(index + 1); + } + }; + + // Return if there's nothing to do + if (!flags || !targets.length) { + return; + } + + // Start setting timestamps + invokeAtIndex(0); +} + /** * Converts the given string or string array into a guaranteed array of strings. * @@ -222,7 +291,7 @@ function getFlags(options: NormalizedTimeOptions): number { * @param times * @param flags */ -function invokeBinding(path: string, times: NormalizedTimeOptions, flags: number, resolveLinks: boolean, callback: Callback): void { +function invokeBindingAsync(path: string, times: NormalizedTimeOptions, flags: number, resolveLinks: boolean, callback: Callback): void { nativeAddon.utimes(getPathBuffer(path), flags, times.btime, times.mtime, times.atime, resolveLinks, (result?: Error) => { if (typeof result !== 'undefined') { const name = resolveLinks ? 'lutimes' : 'utimes'; @@ -235,6 +304,24 @@ function invokeBinding(path: string, times: NormalizedTimeOptions, flags: number }); } +/** + * Calls the binding synchronously. + * + * @param path + * @param times + * @param flags + */ +function invokeBindingSync(path: string, times: NormalizedTimeOptions, flags: number, resolveLinks: boolean): void { + try { + nativeAddon.utimesSync(getPathBuffer(path), flags, times.btime, times.mtime, times.atime, resolveLinks); + } + catch (error) { + const name = resolveLinks ? 'lutimes' : 'utimes'; + const message = (error).message.trim().replace(/\.$/, ''); + throw new Error(`${message}, ${name} '${path}'`); + } +} + /** * Converts a path string into a buffer. * diff --git a/tests/main.test.ts b/tests/main.test.ts index 44e3894..b4519c8 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -42,27 +42,32 @@ describe('File', function() { it('Can change atime', async function() { await utils.testSetTimes(filePath, { atime: 447775200000 }); await utils.testSetTimesCallback(filePath, { atime: 946684800000 }); + utils.testSetTimesSync(filePath, { atime: 447775200000 }); }); it('Can change mtime', async function() { await utils.testSetTimes(filePath, { mtime: 447775200000 }); await utils.testSetTimesCallback(filePath, { mtime: 946684800000 }); + utils.testSetTimesSync(filePath, { mtime: 447775200000 }); }); nonLinuxIt('Can change btime', async function() { await utils.testSetTimes(filePath, { btime: 447775200000 }); await utils.testSetTimesCallback(filePath, { btime: 946684800000 }); + utils.testSetTimesSync(filePath, { btime: 447775200000 }); }); it('Can change two times at once', async function() { await utils.testSetTimes(filePath, { mtime: 223887600000, atime: 223888600000 }); await utils.testSetTimesCallback(filePath, { mtime: 323887600000, atime: 323888600000 }); + utils.testSetTimesSync(filePath, { mtime: 223887600000, atime: 223888600000 }); }); it('Can change all times at once', async function() { await utils.testSetTimes(filePath, { mtime: 447775200000, atime: 447776200000, btime: 447777200000 }); await utils.testSetTimesCallback(filePath, { mtime: 946684800000, atime: 946685800000, btime: 946686800000 }); await utils.testSetTimes(filePath, 946684800000); + utils.testSetTimesSync(filePath, { mtime: 447775200000, atime: 447776200000, btime: 447777200000 }); }); }); @@ -95,27 +100,32 @@ describe('Multiple files', function() { it('Can change atime', async function() { await utils.testSetTimesMulti(files, { atime: 447775200000 }); await utils.testSetTimesMultiCallback(files, { atime: 946684800000 }); + utils.testSetTimesMultiSync(files, { atime: 447775200000 }); }); it('Can change mtime', async function() { await utils.testSetTimesMulti(files, { mtime: 447775200000 }); await utils.testSetTimesMultiCallback(files, { mtime: 946684800000 }); + utils.testSetTimesMultiSync(files, { mtime: 447775200000 }); }); nonLinuxIt('Can change btime', async function() { await utils.testSetTimesMulti(files, { btime: 447775200000 }); await utils.testSetTimesMultiCallback(files, { btime: 946684800000 }); + utils.testSetTimesMultiSync(files, { btime: 447775200000 }); }); it('Can change two times at once', async function() { await utils.testSetTimesMulti(files, { mtime: 223887600000, atime: 223888600000 }); await utils.testSetTimesMultiCallback(files, { mtime: 323887600000, atime: 323888600000 }); + utils.testSetTimesMultiSync(files, { mtime: 223887600000, atime: 223888600000 }); }); it('Can change all times at once', async function() { await utils.testSetTimesMulti(files, { mtime: 447775200000, atime: 447776200000, btime: 447777200000 }); await utils.testSetTimesMultiCallback(files, { mtime: 946684800000, atime: 946685800000, btime: 946686800000 }); await utils.testSetTimesMulti(files, 946684800000); + utils.testSetTimesMultiSync(files, { mtime: 447775200000, atime: 447776200000, btime: 447777200000 }); }); }); @@ -170,6 +180,7 @@ describe('Symbolic link', function() { await utils.assertTimesUnchanged(filePath, async function() { await utils.testSetTimes(symLinkPath!, { atime: 447775200000 }, false); await utils.testSetTimesCallback(symLinkPath!, { atime: 946684800000 }, false); + utils.testSetTimesSync(symLinkPath!, { atime: 447775200000 }, false); }); }); @@ -177,6 +188,7 @@ describe('Symbolic link', function() { await utils.assertTimesUnchanged(symLinkPath!, async function() { await utils.testSetTimes(symLinkPath!, { atime: 447775200000 }); await utils.testSetTimesCallback(symLinkPath!, { atime: 946684800000 }); + utils.testSetTimesSync(symLinkPath!, { atime: 447775200000 }); }, false); }); @@ -184,6 +196,7 @@ describe('Symbolic link', function() { await utils.assertTimesUnchanged(filePath, async function() { await utils.testSetTimes(symLinkPath!, { mtime: 447775200000 }, false); await utils.testSetTimesCallback(symLinkPath!, { mtime: 946684800000 }, false); + utils.testSetTimesSync(symLinkPath!, { mtime: 447775200000 }, false); }); }); @@ -191,6 +204,7 @@ describe('Symbolic link', function() { await utils.assertTimesUnchanged(symLinkPath!, async function() { await utils.testSetTimes(symLinkPath!, { mtime: 447775200000 }); await utils.testSetTimesCallback(symLinkPath!, { mtime: 946684800000 }); + utils.testSetTimesSync(symLinkPath!, { mtime: 447775200000 }); }, false); }); @@ -198,6 +212,7 @@ describe('Symbolic link', function() { await utils.assertTimesUnchanged(filePath, async function() { await utils.testSetTimes(symLinkPath!, { btime: 447775200000 }, false); await utils.testSetTimesCallback(symLinkPath!, { btime: 946684800000 }, false); + utils.testSetTimesSync(symLinkPath!, { btime: 447775200000 }, false); }); }); @@ -205,6 +220,7 @@ describe('Symbolic link', function() { await utils.assertTimesUnchanged(symLinkPath!, async function() { await utils.testSetTimes(symLinkPath!, { btime: 447775200000 }); await utils.testSetTimesCallback(symLinkPath!, { btime: 946684800000 }); + utils.testSetTimesSync(symLinkPath!, { btime: 447775200000 }); }, false); }); diff --git a/tests/utils.ts b/tests/utils.ts index 988c641..237c949 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,4 +1,4 @@ -import { lutimes, utimes } from '../src/main'; +import { lutimes, lutimesSync, utimes, utimesSync } from '../src/main'; import fs from 'fs'; import assert from 'assert'; import util from 'util'; @@ -192,6 +192,19 @@ export async function invokeCallback(filePath: string, times: UTimestampCollecti }); } +/** + * Invokes `utimesSync` or `lutimesSync` with the given parameters. + * + * @param filePath + * @param times + * @param resolveLinks + * @returns + */ +export function invokeSync(filePath: string, times: UTimestampCollection | number, resolveLinks = true) { + const fn = resolveLinks ? utimesSync : lutimesSync; + fn(filePath, times); +} + /** * Returns the current timestamp as an object with `btime`, `mtime`, and `atime`. * @@ -210,6 +223,24 @@ export async function testSetTimes(filePath: string, times: UTimestampCollection assertFileTimes(filePath, expected, resolveLinks); } +/** + * Tests the ability to synchronously set times on the given file. + * + * @param filePath + * @param times + * @param resolveLinks + * @returns + */ +export async function testSetTimesSync(filePath: string, times: UTimestampCollection | number, resolveLinks = true) { + const now = getFileTimes(filePath, resolveLinks); + const expected = mergeTimes(now, times); + + const fn = resolveLinks ? utimesSync : lutimesSync; + fn(filePath, times); + + assertFileTimes(filePath, expected, resolveLinks); +} + /** * Returns the current timestamp as an object with `btime`, `mtime`, and `atime`. * @@ -276,6 +307,22 @@ export async function testSetTimesMultiCallback(filePaths: string[], times: UTim }); } +/** + * Returns the current timestamp as an object with `btime`, `mtime`, and `atime`. + * + * @param filePaths + * @param times + * @returns + */ +export function testSetTimesMultiSync(filePaths: string[], times: UTimestampCollection | number) { + const targets = filePaths.map(path => ({ path, expected: mergeTimes(getFileTimes(path), times) })); + utimesSync(filePaths, times); + + for (const file of targets) { + assertFileTimes(file.path, file.expected); + } +} + export type ResolvedTimestampCollection = { /** * The birth time in milliseconds.