fix(fs): Improve waitForFileToBeReady function to handle directories and file stabilization
This commit is contained in:
parent
be011a4637
commit
e6b8240031
@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-01-07 - 11.1.5 - fix(fs)
|
||||||
|
Improve waitForFileToBeReady function to handle directories and file stabilization
|
||||||
|
|
||||||
|
- Enhanced the waitForFileToBeReady to handle directory paths by checking for file existence within directories and waiting for stabilization.
|
||||||
|
- Modified the watcher logic to cater to changes when monitoring directories for file appearance.
|
||||||
|
- Introduced a helper function to ensure paths exist and another to resolve the first file in directories.
|
||||||
|
- Corrected logic for polling and stabilizing files within directories.
|
||||||
|
|
||||||
## 2025-01-07 - 11.1.4 - fix(fs)
|
## 2025-01-07 - 11.1.4 - fix(fs)
|
||||||
Fix file existence check in waitForFileToBeReady method.
|
Fix file existence check in waitForFileToBeReady method.
|
||||||
|
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartfile',
|
name: '@push.rocks/smartfile',
|
||||||
version: '11.1.4',
|
version: '11.1.5',
|
||||||
description: 'Provides comprehensive tools for efficient file management in Node.js using TypeScript, including handling streams, virtual directories, and various file operations.'
|
description: 'Provides comprehensive tools for efficient file management in Node.js using TypeScript, including handling streams, virtual directories, and various file operations.'
|
||||||
}
|
}
|
||||||
|
155
ts/fs.ts
155
ts/fs.ts
@ -397,49 +397,67 @@ export const listFileTree = async (
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Watches for file stability before resolving the promise.
|
* Watches for file stability before resolving the promise.
|
||||||
* Ensures that the directory and file exist before setting up the watcher.
|
* Ensures that the directory/file exists before setting up the watcher.
|
||||||
* @param filePathArg The path of the file to monitor.
|
*
|
||||||
|
* **New behavior**: If the given path is a directory, this function will:
|
||||||
|
* 1. Wait for that directory to exist (creating a timeout if needed).
|
||||||
|
* 2. Watch the directory until at least one file appears.
|
||||||
|
* 3. Then wait for the first file in the directory to stabilize before resolving.
|
||||||
|
*
|
||||||
|
* @param fileOrDirPathArg The path of the file or directory to monitor.
|
||||||
* @param timeoutMs The maximum time to wait for the file to stabilize (in milliseconds). Default is 60 seconds.
|
* @param timeoutMs The maximum time to wait for the file to stabilize (in milliseconds). Default is 60 seconds.
|
||||||
* @returns A promise that resolves when the file is stable or rejects on timeout or error.
|
* @returns A promise that resolves when the target is stable or rejects on timeout/error.
|
||||||
*/
|
*/
|
||||||
export const waitForFileToBeReady = (
|
export const waitForFileToBeReady = async (
|
||||||
filePathArg: string,
|
fileOrDirPathArg: string,
|
||||||
timeoutMs: number = 60000
|
timeoutMs: number = 60000
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
let lastFileSize = -1;
|
|
||||||
let fileIsStable = false;
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const fileDir = plugins.path.dirname(filePathArg);
|
|
||||||
|
|
||||||
const ensureFileExists = async () => {
|
/**
|
||||||
|
* Ensure that a path (file or directory) exists. If it doesn't yet exist,
|
||||||
|
* wait until it does (or time out).
|
||||||
|
* @param pathToCheck The file or directory path to check.
|
||||||
|
*/
|
||||||
|
const ensurePathExists = async (pathToCheck: string): Promise<void> => {
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
// Check if the file exists
|
|
||||||
await plugins.smartpromise.fromCallback((cb) =>
|
await plugins.smartpromise.fromCallback((cb) =>
|
||||||
plugins.fs.access(filePathArg, plugins.fs.constants.F_OK, cb)
|
plugins.fs.access(pathToCheck, plugins.fs.constants.F_OK, cb)
|
||||||
);
|
);
|
||||||
break; // Exit the loop if the file exists
|
return;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.code !== 'ENOENT') {
|
if (err.code !== 'ENOENT') {
|
||||||
throw err; // Propagate unexpected errors
|
throw err; // Propagate unexpected errors
|
||||||
}
|
}
|
||||||
if (Date.now() - startTime > timeoutMs) {
|
if (Date.now() - startTime > timeoutMs) {
|
||||||
reject(new Error(`Timeout waiting for file to exist: ${filePathArg}`));
|
throw new Error(`Timeout waiting for path to exist: ${pathToCheck}`);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// Wait and retry
|
|
||||||
await plugins.smartdelay.delayFor(500);
|
await plugins.smartdelay.delayFor(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a file (not directory) is stable by comparing sizes
|
||||||
|
* across successive checks.
|
||||||
|
* @param filePathArg The path of the file to check.
|
||||||
|
* @returns A promise that resolves once the file stops changing.
|
||||||
|
*/
|
||||||
|
const waitForSingleFileToBeStable = async (filePathArg: string): Promise<void> => {
|
||||||
|
let lastFileSize = -1;
|
||||||
|
let fileIsStable = false;
|
||||||
|
|
||||||
|
// We'll create a helper for repeated stats-checking logic
|
||||||
const checkFileStability = async () => {
|
const checkFileStability = async () => {
|
||||||
try {
|
try {
|
||||||
const stats = await plugins.smartpromise.fromCallback<plugins.fs.Stats>((cb) =>
|
const stats = await plugins.smartpromise.fromCallback<plugins.fs.Stats>((cb) =>
|
||||||
plugins.fs.stat(filePathArg, cb)
|
plugins.fs.stat(filePathArg, cb)
|
||||||
);
|
);
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
// If it unexpectedly turns out to be a directory here, throw
|
||||||
|
throw new Error(`Expected a file but found a directory: ${filePathArg}`);
|
||||||
|
}
|
||||||
if (stats.size === lastFileSize) {
|
if (stats.size === lastFileSize) {
|
||||||
fileIsStable = true;
|
fileIsStable = true;
|
||||||
} else {
|
} else {
|
||||||
@ -447,49 +465,106 @@ export const waitForFileToBeReady = (
|
|||||||
fileIsStable = false;
|
fileIsStable = false;
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
// Ignore only if file not found
|
||||||
if (err.code !== 'ENOENT') {
|
if (err.code !== 'ENOENT') {
|
||||||
throw err; // Only ignore ENOENT (file not found) errors
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure the file exists before setting up the watcher
|
// Ensure file exists first
|
||||||
await ensureFileExists();
|
await ensurePathExists(filePathArg);
|
||||||
|
|
||||||
const watcher = plugins.fs.watch(filePathArg, { persistent: true }, async () => {
|
// Set up a watcher on the file itself
|
||||||
|
const fileWatcher = plugins.fs.watch(filePathArg, { persistent: true }, async () => {
|
||||||
if (!fileIsStable) {
|
if (!fileIsStable) {
|
||||||
await checkFileStability();
|
await checkFileStability();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watcher.on('error', (error) => {
|
|
||||||
watcher.close();
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Poll until stable or timeout
|
||||||
while (!fileIsStable) {
|
while (!fileIsStable) {
|
||||||
// Check for timeout
|
|
||||||
if (Date.now() - startTime > timeoutMs) {
|
if (Date.now() - startTime > timeoutMs) {
|
||||||
watcher.close();
|
throw new Error(`Timeout waiting for file to stabilize: ${filePathArg}`);
|
||||||
reject(new Error(`Timeout waiting for file to stabilize: ${filePathArg}`));
|
}
|
||||||
|
await checkFileStability();
|
||||||
|
if (!fileIsStable) {
|
||||||
|
await plugins.smartdelay.delayFor(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fileWatcher.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main logic: check if we have a directory or file at fileOrDirPathArg.
|
||||||
|
* If directory, wait for first file in the directory to appear and stabilize.
|
||||||
|
* If file, do the old single-file wait logic.
|
||||||
|
*/
|
||||||
|
const statsForGivenPath = await (async () => {
|
||||||
|
try {
|
||||||
|
await ensurePathExists(fileOrDirPathArg);
|
||||||
|
return await plugins.smartpromise.fromCallback<plugins.fs.Stats>((cb) =>
|
||||||
|
plugins.fs.stat(fileOrDirPathArg, cb)
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
// If there's an error (including timeout), just rethrow
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (!statsForGivenPath.isDirectory()) {
|
||||||
|
// It's a file – just do the single-file stability wait
|
||||||
|
await waitForSingleFileToBeStable(fileOrDirPathArg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check file stability
|
// Otherwise, it's a directory. Wait for the first file inside to appear and be stable
|
||||||
await checkFileStability();
|
const dirPath = fileOrDirPathArg;
|
||||||
if (!fileIsStable) {
|
|
||||||
await plugins.smartdelay.delayFor(1000); // Polling interval
|
// Helper to find the first file in the directory if it exists
|
||||||
|
const getFirstFileInDirectory = async (): Promise<string | null> => {
|
||||||
|
const entries = await plugins.smartpromise.fromCallback<string[]>((cb) =>
|
||||||
|
plugins.fs.readdir(dirPath, cb)
|
||||||
|
);
|
||||||
|
// We only want actual files, not subdirectories
|
||||||
|
for (const entry of entries) {
|
||||||
|
const entryPath = plugins.path.join(dirPath, entry);
|
||||||
|
const entryStats = await plugins.smartpromise.fromCallback<plugins.fs.Stats>((cb) =>
|
||||||
|
plugins.fs.stat(entryPath, cb)
|
||||||
|
);
|
||||||
|
if (entryStats.isFile()) {
|
||||||
|
return entryPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wait for a file to appear in this directory
|
||||||
|
let firstFilePath = await getFirstFileInDirectory();
|
||||||
|
if (!firstFilePath) {
|
||||||
|
// Set up a watcher on the directory to see if a file appears
|
||||||
|
const directoryWatcher = plugins.fs.watch(dirPath, { persistent: true });
|
||||||
|
try {
|
||||||
|
// We'll poll for the existence of a file in that directory
|
||||||
|
while (!firstFilePath) {
|
||||||
|
if (Date.now() - startTime > timeoutMs) {
|
||||||
|
throw new Error(`Timeout waiting for a file to appear in directory: ${dirPath}`);
|
||||||
|
}
|
||||||
|
firstFilePath = await getFirstFileInDirectory();
|
||||||
|
if (!firstFilePath) {
|
||||||
|
await plugins.smartdelay.delayFor(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
directoryWatcher.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watcher.close();
|
// Now that we have a file path, wait for that file to stabilize
|
||||||
resolve();
|
await waitForSingleFileToBeStable(firstFilePath);
|
||||||
} catch (err) {
|
|
||||||
watcher.close();
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user