Skip to content

Commit

Permalink
Add synchronous functions
Browse files Browse the repository at this point in the history
  • Loading branch information
baileyherbert committed Mar 16, 2022
1 parent 53077f3 commit 6acea8d
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 3 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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!
29 changes: 29 additions & 0 deletions cpp/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<char> pathHandle = info[0].As<Napi::Buffer<char>>();
const uint8_t flags = info[1].As<Napi::Number>().Uint32Value();
const uint64_t btime = info[2].As<Napi::Number>().Int64Value();
const uint64_t mtime = info[3].As<Napi::Number>().Int64Value();
const uint64_t atime = info[4].As<Napi::Number>().Int64Value();
const bool resolveLinks = info[5].As<Napi::Boolean>().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<utimes>(env)
);

exports.Set(
Napi::String::New(env, "utimesSync"),
Napi::Function::New<utimesSync>(env)
);

return exports;
}

Expand Down
91 changes: 89 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
*
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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';
Expand All @@ -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 = (<any>error).message.trim().replace(/\.$/, '');
throw new Error(`${message}, ${name} '${path}'`);
}
}

/**
* Converts a path string into a buffer.
*
Expand Down
16 changes: 16 additions & 0 deletions tests/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
});
});

Expand Down Expand Up @@ -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 });
});
});

Expand Down Expand Up @@ -170,41 +180,47 @@ 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);
});
});

wrappedIt('Can change atime without affecting symlink', async 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);
});

wrappedIt('Can change mtime', async 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);
});
});

wrappedIt('Can change mtime without affecting symlink', async 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);
});

wrappedLinuxIt('Can change btime', async 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);
});
});

wrappedLinuxIt('Can change btime without affecting symlink', async 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);
});

Expand Down
49 changes: 48 additions & 1 deletion tests/utils.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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`.
*
Expand All @@ -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`.
*
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 6acea8d

Please sign in to comment.