Skip to content

Commit

Permalink
Changed stats cache to work on promises
Browse files Browse the repository at this point in the history
  • Loading branch information
james-pre committed Oct 31, 2024
1 parent 994c282 commit 9c9eb96
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 26 deletions.
35 changes: 32 additions & 3 deletions src/emulation/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,41 @@ export function setEnabled(value: boolean): void {
isEnabled = value;
}

const stats = new Map<string, Stats>();
const statsSync = new Map<string, Stats>();

/**
* 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<string, Promise<Stats | undefined>>();

/**
* Gets stats from the cache, if they exist and the cache is enabled.
*/
export function getStats(path: string): Promise<Stats | undefined> | undefined {
if (!isEnabled) return;

return stats.get(path);
Expand All @@ -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<Stats | undefined>): void {
if (!isEnabled) return;

stats.set(path, value);
Expand Down
35 changes: 23 additions & 12 deletions src/emulation/promises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ export async function unlink(path: fs.PathLike): Promise<void> {
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);
Expand Down Expand Up @@ -624,7 +624,10 @@ export async function rmdir(path: fs.PathLike): Promise<void> {
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');
}
Expand Down Expand Up @@ -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');
Expand All @@ -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!));
Expand Down Expand Up @@ -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) {
Expand Down
22 changes: 11 additions & 11 deletions src/emulation/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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');
}
Expand Down Expand Up @@ -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');
}
Expand All @@ -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));
Expand All @@ -513,7 +513,7 @@ export function readdirSync(
}

if (!options?._isIndirect) {
cache.clearStats();
cache.clearStatsSync();
}
return values as string[] | Dirent[] | Buffer[];
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -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:
Expand All @@ -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;
Expand Down

0 comments on commit 9c9eb96

Please sign in to comment.