From 9c9eb9630ba543eacee45776b059951cf280fdc2 Mon Sep 17 00:00:00 2001 From: James Prevett Date: Wed, 30 Oct 2024 22:40:03 -0500 Subject: [PATCH] Changed stats cache to work on promises --- src/emulation/cache.ts | 35 ++++++++++++++++++++++++++++++++--- src/emulation/promises.ts | 35 +++++++++++++++++++++++------------ src/emulation/sync.ts | 22 +++++++++++----------- 3 files changed, 66 insertions(+), 26 deletions(-) diff --git a/src/emulation/cache.ts b/src/emulation/cache.ts index c216a060..bb5ff8bc 100644 --- a/src/emulation/cache.ts +++ b/src/emulation/cache.ts @@ -14,12 +14,41 @@ export function setEnabled(value: boolean): void { isEnabled = value; } -const stats = new Map(); +const statsSync = new Map(); /** * Gets stats from the cache, if they exist and the cache is enabled. */ -export function getStats(path: string): Stats | undefined { +export function getStatsSync(path: string): Stats | undefined { + if (!isEnabled) return; + + return statsSync.get(path); +} + +/** + * Adds stats if the cache is enabled + */ +export function setStatsSync(path: string, value: Stats): void { + if (!isEnabled) return; + + statsSync.set(path, value); +} + +/** + * Clears the cache if it is enabled + */ +export function clearStatsSync(): void { + if (!isEnabled) return; + + statsSync.clear(); +} + +const stats = new Map>(); + +/** + * Gets stats from the cache, if they exist and the cache is enabled. + */ +export function getStats(path: string): Promise | undefined { if (!isEnabled) return; return stats.get(path); @@ -28,7 +57,7 @@ export function getStats(path: string): Stats | undefined { /** * Adds stats if the cache is enabled */ -export function setStats(path: string, value: Stats): void { +export function setStats(path: string, value: Promise): void { if (!isEnabled) return; stats.set(path, value); diff --git a/src/emulation/promises.ts b/src/emulation/promises.ts index e184ac5d..3511d430 100644 --- a/src/emulation/promises.ts +++ b/src/emulation/promises.ts @@ -471,7 +471,7 @@ export async function unlink(path: fs.PathLike): Promise { path = normalizePath(path); const { fs, path: resolved } = resolveMount(path); try { - if (config.checkAccess && !(cache.getStats(path) || (await fs.stat(resolved))).hasAccess(constants.W_OK)) { + if (config.checkAccess && !(await (cache.getStats(path) || fs.stat(resolved)))!.hasAccess(constants.W_OK)) { throw ErrnoError.With('EACCES', resolved, 'unlink'); } await fs.unlink(resolved); @@ -624,7 +624,10 @@ export async function rmdir(path: fs.PathLike): Promise { path = await realpath(path); const { fs, path: resolved } = resolveMount(path); try { - const stats = cache.getStats(path) || (await fs.stat(resolved)); + const stats = await (cache.getStats(path) || fs.stat(resolved)); + if (!stats) { + throw ErrnoError.With('ENOENT', path, 'readdir'); + } if (!stats.isDirectory()) { throw ErrnoError.With('ENOTDIR', resolved, 'rmdir'); } @@ -716,8 +719,13 @@ export async function readdir( const { fs, path: resolved } = resolveMount(path); - const stats = cache.getStats(path) || (await fs.stat(resolved).catch(handleError)); - cache.setStats(path, stats); + const _stats = cache.getStats(path) || fs.stat(resolved).catch(handleError); + cache.setStats(path, _stats); + const stats = await _stats; + + if (!stats) { + throw ErrnoError.With('ENOENT', path, 'readdir'); + } if (config.checkAccess && !stats.hasAccess(constants.R_OK)) { throw ErrnoError.With('EACCES', path, 'readdir'); @@ -744,8 +752,9 @@ export async function readdir( const addEntry = async (entry: string) => { let entryStats: Stats | undefined; if (options?.recursive || options?.withFileTypes) { - entryStats = cache.getStats(join(path, entry)) || (await fs.stat(join(resolved, entry)).catch(handleError)); - cache.setStats(join(path, entry), entryStats); + const _entryStats = cache.getStats(join(path, entry)) || fs.stat(join(resolved, entry)).catch(handleError); + cache.setStats(join(path, entry), _entryStats); + entryStats = await _entryStats; } if (options?.withFileTypes) { values.push(new Dirent(entry, entryStats!)); @@ -968,18 +977,20 @@ access satisfies typeof promises.access; export async function rm(path: fs.PathLike, options?: fs.RmOptions & InternalOptions) { path = normalizePath(path); - const stats = + const _stats = cache.getStats(path) || - (await stat(path).catch((error: ErrnoError) => { - if (error.code != 'ENOENT' || !options?.force) throw error; - })); + stat(path).catch((error: ErrnoError) => { + if (error.code == 'ENOENT' && options?.force) return undefined; + throw error; + }); + + cache.setStats(path, _stats); + const stats = await _stats; if (!stats) { return; } - cache.setStats(path, stats); - switch (stats.mode & constants.S_IFMT) { case constants.S_IFDIR: if (options?.recursive) { diff --git a/src/emulation/sync.ts b/src/emulation/sync.ts index 1a9f3f80..452451b2 100644 --- a/src/emulation/sync.ts +++ b/src/emulation/sync.ts @@ -106,7 +106,7 @@ export function unlinkSync(path: fs.PathLike): void { path = normalizePath(path); const { fs, path: resolved } = resolveMount(path); try { - if (config.checkAccess && !(cache.getStats(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) { + if (config.checkAccess && !(cache.getStatsSync(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) { throw ErrnoError.With('EACCES', resolved, 'unlink'); } fs.unlinkSync(resolved); @@ -387,7 +387,7 @@ export function rmdirSync(path: fs.PathLike): void { path = normalizePath(path); const { fs, path: resolved } = resolveMount(realpathSync(path)); try { - const stats = cache.getStats(path) || fs.statSync(resolved); + const stats = cache.getStatsSync(path) || fs.statSync(resolved); if (!stats.isDirectory()) { throw ErrnoError.With('ENOTDIR', resolved, 'rmdir'); } @@ -460,8 +460,8 @@ export function readdirSync( const { fs, path: resolved } = resolveMount(realpathSync(path)); let entries: string[]; try { - const stats = cache.getStats(path) || fs.statSync(resolved); - cache.setStats(path, stats); + const stats = cache.getStatsSync(path) || fs.statSync(resolved); + cache.setStatsSync(path, stats); if (config.checkAccess && !stats.hasAccess(constants.R_OK)) { throw ErrnoError.With('EACCES', resolved, 'readdir'); } @@ -488,8 +488,8 @@ export function readdirSync( // Iterate over entries and handle recursive case if needed const values: (string | Dirent | Buffer)[] = []; for (const entry of entries) { - const entryStat = cache.getStats(join(path, entry)) || fs.statSync(join(resolved, entry)); - cache.setStats(join(path, entry), entryStat); + const entryStat = cache.getStatsSync(join(path, entry)) || fs.statSync(join(resolved, entry)); + cache.setStatsSync(join(path, entry), entryStat); if (options?.withFileTypes) { values.push(new Dirent(entry, entryStat)); @@ -513,7 +513,7 @@ export function readdirSync( } if (!options?._isIndirect) { - cache.clearStats(); + cache.clearStatsSync(); } return values as string[] | Dirent[] | Buffer[]; } @@ -671,7 +671,7 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio let stats: Stats | undefined; try { - stats = cache.getStats(path) || statSync(path); + stats = cache.getStatsSync(path) || statSync(path); } catch (error) { if ((error as ErrnoError).code != 'ENOENT' || !options?.force) throw error; } @@ -680,7 +680,7 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio return; } - cache.setStats(path, stats); + cache.setStatsSync(path, stats); switch (stats.mode & constants.S_IFMT) { case constants.S_IFDIR: @@ -701,12 +701,12 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio case constants.S_IFIFO: case constants.S_IFSOCK: default: - cache.clearStats(); + cache.clearStatsSync(); throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm'); } if (!options?._isIndirect) { - cache.clearStats(); + cache.clearStatsSync(); } } rmSync satisfies typeof fs.rmSync;