smartfile/ts/fs.ts

539 lines
15 KiB
TypeScript
Raw Normal View History

2024-04-02 20:53:02 +02:00
import * as plugins from './plugins.js';
2023-11-04 20:07:43 +01:00
import * as interpreter from './interpreter.js';
2023-11-04 20:07:43 +01:00
import { SmartFile } from './classes.smartfile.js';
2023-11-04 20:07:43 +01:00
import * as memory from './memory.js';
2024-04-02 20:53:02 +02:00
import type { StreamFile } from './classes.streamfile.js';
2016-06-24 03:36:51 +02:00
/*===============================================================
============================ Checks =============================
===============================================================*/
/**
*
* @param filePath
* @returns {boolean}
*/
2019-06-27 10:42:35 +02:00
export const fileExistsSync = (filePath): boolean => {
let fileExistsBool: boolean = false;
try {
plugins.fsExtra.readFileSync(filePath);
fileExistsBool = true;
} catch (err) {
fileExistsBool = false;
}
return fileExistsBool;
};
2016-06-24 03:36:51 +02:00
/**
*
* @param filePath
* @returns {any}
*/
2019-09-27 11:00:17 +02:00
export const fileExists = async (filePath): Promise<boolean> => {
const done = plugins.smartpromise.defer<boolean>();
plugins.fs.access(filePath, 4, (err) => {
err ? done.resolve(false) : done.resolve(true);
});
return done.promise;
};
2016-06-24 03:36:51 +02:00
/**
* Checks if given path points to an existing directory
*/
2023-08-23 10:58:38 +02:00
export const isDirectory = (pathArg: string): boolean => {
try {
return plugins.fsExtra.statSync(pathArg).isDirectory();
} catch (err) {
return false;
}
};
/**
* Checks if given path points to an existing directory
*/
export const isDirectorySync = (pathArg: string): boolean => {
try {
return plugins.fsExtra.statSync(pathArg).isDirectory();
} catch (err) {
return false;
}
};
2016-06-24 03:36:51 +02:00
/**
* Checks if a given path points to an existing file
*/
2019-06-27 10:42:35 +02:00
export const isFile = (pathArg): boolean => {
return plugins.fsExtra.statSync(pathArg).isFile();
};
2016-06-24 03:36:51 +02:00
2016-06-23 17:42:08 +02:00
/*===============================================================
============================ FS ACTIONS =========================
===============================================================*/
2016-09-24 21:42:45 +02:00
/**
* copies a file from A to B on the local disk
*/
2019-06-27 10:42:35 +02:00
export const copy = async (fromArg: string, toArg: string): Promise<boolean> => {
const done = plugins.smartpromise.defer<boolean>();
plugins.fsExtra.copy(fromArg, toArg, {}, (err) => {
2019-06-27 10:42:35 +02:00
if (err) {
throw new Error(`Could not copy from ${fromArg} to ${toArg}: ${err}`);
}
done.resolve(true);
});
return done.promise;
};
2016-09-24 21:42:45 +02:00
/**
* copies a file SYNCHRONOUSLY from A to B on the local disk
*/
2019-06-27 10:42:35 +02:00
export const copySync = (fromArg: string, toArg: string): boolean => {
plugins.fsExtra.copySync(fromArg, toArg);
return true;
};
2016-09-24 21:42:45 +02:00
2016-06-28 06:57:51 +02:00
/**
* ensures that a directory is in place
*/
2019-09-27 11:00:17 +02:00
export const ensureDir = async (dirPathArg: string) => {
await plugins.fsExtra.ensureDir(dirPathArg);
};
2016-06-28 06:57:51 +02:00
/**
* ensures that a directory is in place
*/
2019-09-27 11:00:17 +02:00
export const ensureDirSync = (dirPathArg: string) => {
plugins.fsExtra.ensureDirSync(dirPathArg);
};
2016-06-28 06:57:51 +02:00
/**
* ensure an empty directory
* @executes ASYNC
*/
2019-09-27 11:00:17 +02:00
export const ensureEmptyDir = async (dirPathArg: string) => {
await plugins.fsExtra.ensureDir(dirPathArg);
await plugins.fsExtra.emptyDir(dirPathArg);
};
/**
* ensure an empty directory
* @executes SYNC
*/
2019-09-27 11:00:17 +02:00
export const ensureEmptyDirSync = (dirPathArg: string) => {
plugins.fsExtra.ensureDirSync(dirPathArg);
plugins.fsExtra.emptyDirSync(dirPathArg);
};
2016-06-23 18:39:02 +02:00
/**
2016-09-24 21:42:45 +02:00
* ensures that a file is on disk
* @param filePath the filePath to ensureDir
* @param the fileContent to place into a new file in case it doesn't exist yet
* @returns Promise<void>
* @exec ASYNC
2016-06-23 18:39:02 +02:00
*/
2019-09-27 11:00:17 +02:00
export const ensureFile = async (filePathArg, initFileStringArg): Promise<void> => {
ensureFileSync(filePathArg, initFileStringArg);
};
2016-06-23 17:42:08 +02:00
2016-06-23 18:39:02 +02:00
/**
2016-09-24 21:42:45 +02:00
* ensures that a file is on disk
* @param filePath the filePath to ensureDir
* @param the fileContent to place into a new file in case it doesn't exist yet
* @returns Promise<void>
* @exec SYNC
2016-06-23 18:39:02 +02:00
*/
2019-09-27 11:00:17 +02:00
export const ensureFileSync = (filePathArg: string, initFileStringArg: string): void => {
if (fileExistsSync(filePathArg)) {
return null;
} else {
memory.toFsSync(initFileStringArg, filePathArg);
}
};
2016-09-20 17:56:49 +02:00
2016-09-24 21:42:45 +02:00
/**
* removes a file or folder from local disk
*/
2019-09-27 11:00:17 +02:00
export const remove = async (pathArg: string): Promise<void> => {
await plugins.fsExtra.remove(pathArg);
};
2016-06-23 17:42:08 +02:00
2016-06-23 18:39:02 +02:00
/**
* removes a file SYNCHRONOUSLY from local disk
*/
2019-09-27 11:00:17 +02:00
export const removeSync = (pathArg: string): void => {
plugins.fsExtra.removeSync(pathArg);
};
2016-06-23 17:42:08 +02:00
2016-09-29 14:17:46 +02:00
/**
* removes an array of filePaths from disk
*/
2019-09-27 11:00:17 +02:00
export const removeMany = async (filePathArrayArg: string[]) => {
const promiseArray: Array<Promise<void>> = [];
for (const filePath of filePathArrayArg) {
promiseArray.push(remove(filePath));
}
2019-09-27 11:00:17 +02:00
await Promise.all(promiseArray);
};
2016-09-29 14:17:46 +02:00
/**
* like removeFilePathArray but SYNCHRONOUSLY
*/
2019-09-27 11:00:17 +02:00
export const removeManySync = (filePathArrayArg: string[]): void => {
for (const filePath of filePathArrayArg) {
removeSync(filePath);
}
};
2016-09-29 14:17:46 +02:00
2016-06-23 17:42:08 +02:00
/*===============================================================
============================ Write/Read =========================
===============================================================*/
/**
2024-06-06 22:29:06 +02:00
* reads a file content to an object
* good for JSON, YAML, TOML, etc.
2016-06-23 17:42:08 +02:00
* @param filePathArg
* @param fileTypeArg
* @returns {any}
*/
2019-09-27 11:00:17 +02:00
export const toObjectSync = (filePathArg, fileTypeArg?) => {
2019-01-27 03:11:10 +01:00
const fileString = plugins.fsExtra.readFileSync(filePathArg, 'utf8');
let fileType;
2020-10-05 16:20:57 +00:00
fileTypeArg ? (fileType = fileTypeArg) : (fileType = interpreter.filetype(filePathArg));
try {
return interpreter.objectFile(fileString, fileType);
} catch (err) {
err.message = `Failed to read file at ${filePathArg}` + err.message;
throw err;
}
};
2016-06-23 17:42:08 +02:00
/**
* reads a file content to a String
*/
2019-06-27 10:42:35 +02:00
export const toStringSync = (filePath: string): string => {
2024-05-28 12:43:46 +02:00
const encoding = plugins.smartmime.getEncodingForPathSync(filePath);
2020-10-05 16:20:57 +00:00
let fileString: string | Buffer = plugins.fsExtra.readFileSync(filePath, encoding);
if (Buffer.isBuffer(fileString)) {
fileString = fileString.toString('binary');
}
return fileString;
};
2021-11-30 16:34:35 +01:00
export const toBuffer = async (filePath: string): Promise<Buffer> => {
return plugins.fsExtra.readFile(filePath);
};
2020-03-04 16:31:13 +00:00
export const toBufferSync = (filePath: string): Buffer => {
return plugins.fsExtra.readFileSync(filePath);
2020-03-15 18:58:46 +00:00
};
2020-03-04 16:31:13 +00:00
2023-11-03 00:41:05 +01:00
/**
* Creates a Readable Stream from a file path.
* @param filePath The path to the file.
* @returns {fs.ReadStream}
*/
export const toReadStream = (filePath: string): plugins.fs.ReadStream => {
if (!fileExistsSync(filePath)) {
throw new Error(`File does not exist at path: ${filePath}`);
}
return plugins.fsExtra.createReadStream(filePath);
};
2019-09-27 11:00:17 +02:00
export const fileTreeToHash = async (dirPathArg: string, miniMatchFilter: string) => {
const fileTreeObject = await fileTreeToObject(dirPathArg, miniMatchFilter);
let combinedString = '';
for (const smartfile of fileTreeObject) {
2023-01-09 15:32:37 +01:00
combinedString += await smartfile.getHash();
2019-09-27 11:00:17 +02:00
}
const hash = await plugins.smarthash.sha256FromString(combinedString);
return hash;
};
/**
* creates a smartfile array from a directory
* @param dirPathArg the directory to start from
* @param miniMatchFilter a minimatch filter of what files to include
*/
2019-06-26 21:17:58 +02:00
export const fileTreeToObject = async (dirPathArg: string, miniMatchFilter: string) => {
2017-04-30 15:37:34 +02:00
// handle absolute miniMatchFilter
let dirPath: string;
2017-04-30 15:37:34 +02:00
if (plugins.path.isAbsolute(miniMatchFilter)) {
dirPath = '/';
2017-04-30 15:37:34 +02:00
} else {
2022-06-07 15:43:28 +02:00
dirPath = plugins.smartpath.transform.toAbsolute(dirPathArg) as string;
2017-04-30 15:37:34 +02:00
}
2019-06-26 21:17:58 +02:00
const fileTree = await listFileTree(dirPath, miniMatchFilter);
2023-11-04 20:07:43 +01:00
const smartfileArray: SmartFile[] = [];
2019-09-27 11:00:17 +02:00
for (const filePath of fileTree) {
const readPath = ((): string => {
2017-08-02 13:10:30 +02:00
if (!plugins.path.isAbsolute(filePath)) {
return plugins.path.join(dirPath, filePath);
2017-08-02 13:10:30 +02:00
} else {
return filePath;
2017-08-02 13:10:30 +02:00
}
})();
2023-08-31 18:45:23 +02:00
const fileBuffer = plugins.fs.readFileSync(readPath);
2017-05-01 19:49:34 +02:00
2017-05-07 23:16:25 +02:00
// push a read file as Smartfile
smartfileArray.push(
2023-11-04 20:07:43 +01:00
new SmartFile({
2023-08-31 18:45:23 +02:00
contentBuffer: fileBuffer,
base: dirPath,
path: filePath,
})
);
}
return smartfileArray;
};
2016-06-23 17:42:08 +02:00
2016-06-23 18:39:02 +02:00
/**
* lists Folders in a directory on local disk
2018-11-22 23:23:26 +01:00
* @returns Promise with an array that contains the folder names
2016-06-23 18:39:02 +02:00
*/
2019-09-27 11:00:17 +02:00
export const listFolders = async (pathArg: string, regexFilter?: RegExp): Promise<string[]> => {
2019-01-27 03:11:10 +01:00
return listFoldersSync(pathArg, regexFilter);
};
2016-06-23 17:42:08 +02:00
2016-06-23 18:39:02 +02:00
/**
* lists Folders SYNCHRONOUSLY in a directory on local disk
2016-06-28 06:57:51 +02:00
* @returns an array with the folder names as strings
2016-06-23 18:39:02 +02:00
*/
2019-09-27 11:00:17 +02:00
export const listFoldersSync = (pathArg: string, regexFilter?: RegExp): string[] => {
let folderArray = plugins.fsExtra.readdirSync(pathArg).filter((file) => {
return plugins.fsExtra.statSync(plugins.path.join(pathArg, file)).isDirectory();
});
if (regexFilter) {
folderArray = folderArray.filter((fileItem) => {
return regexFilter.test(fileItem);
});
}
return folderArray;
};
/**
* lists Files in a directory on local disk
* @returns Promise
*/
2019-09-27 11:00:17 +02:00
export const listFiles = async (pathArg: string, regexFilter?: RegExp): Promise<string[]> => {
2019-01-27 03:11:10 +01:00
return listFilesSync(pathArg, regexFilter);
};
/**
* lists Files SYNCHRONOUSLY in a directory on local disk
* @returns an array with the folder names as strings
*/
2019-09-27 11:00:17 +02:00
export const listFilesSync = (pathArg: string, regexFilter?: RegExp): string[] => {
let fileArray = plugins.fsExtra.readdirSync(pathArg).filter((file) => {
return plugins.fsExtra.statSync(plugins.path.join(pathArg, file)).isFile();
});
if (regexFilter) {
fileArray = fileArray.filter((fileItem) => {
return regexFilter.test(fileItem);
});
}
return fileArray;
};
/**
* lists all items (folders AND files) in a directory on local disk
2016-09-17 23:11:44 +02:00
* @returns Promise<string[]>
*/
2019-09-27 11:00:17 +02:00
export const listAllItems = async (pathArg: string, regexFilter?: RegExp): Promise<string[]> => {
2019-01-27 03:11:10 +01:00
return listAllItemsSync(pathArg, regexFilter);
};
/**
2016-09-17 23:11:44 +02:00
* lists all items (folders AND files) in a directory on local disk
* @returns an array with the folder names as strings
2016-09-17 23:11:44 +02:00
* @executes SYNC
*/
2019-09-27 11:00:17 +02:00
export const listAllItemsSync = (pathArg: string, regexFilter?: RegExp): string[] => {
let allItmesArray = plugins.fsExtra.readdirSync(pathArg).filter((file) => {
return plugins.fsExtra.statSync(plugins.path.join(pathArg, file)).isFile();
});
if (regexFilter) {
allItmesArray = allItmesArray.filter((fileItem) => {
return regexFilter.test(fileItem);
});
}
return allItmesArray;
};
2016-09-17 23:11:44 +02:00
/**
* lists a file tree using a miniMatch filter
2016-09-30 16:16:11 +02:00
* note: if the miniMatch Filter is an absolute path, the cwdArg will be omitted
2016-09-17 23:11:44 +02:00
* @returns Promise<string[]> string array with the absolute paths of all matching files
*/
2019-09-27 11:00:17 +02:00
export const listFileTree = async (
dirPathArg: string,
miniMatchFilter: string,
absolutePathsBool: boolean = false
): Promise<string[]> => {
// handle absolute miniMatchFilter
let dirPath: string;
if (plugins.path.isAbsolute(miniMatchFilter)) {
dirPath = '/';
} else {
dirPath = dirPathArg;
}
2019-01-27 03:11:10 +01:00
const options = {
2017-05-27 01:07:32 +02:00
cwd: dirPath,
2017-05-27 02:54:22 +02:00
nodir: true,
dot: true,
};
2019-09-27 11:00:17 +02:00
2023-06-23 18:43:49 +02:00
let fileList = await plugins.glob.glob(miniMatchFilter, options);
2019-06-26 21:17:58 +02:00
if (absolutePathsBool) {
fileList = fileList.map((filePath) => {
2019-06-26 21:17:58 +02:00
return plugins.path.resolve(plugins.path.join(dirPath, filePath));
});
}
return fileList;
};
2021-12-03 00:24:10 +01:00
/**
2023-11-07 14:09:48 +01:00
* Watches for file stability before resolving the promise.
* @param filePathArg The path of the file to monitor.
* @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.
2021-12-03 00:24:10 +01:00
*/
export const waitForFileToBeReady = (
filePathArg: string,
timeoutMs: number = 60000
): Promise<void> => {
2023-11-24 19:15:41 +01:00
return new Promise(async (resolve, reject) => {
let lastFileSize = -1;
let fileIsStable = false;
const startTime = Date.now();
const fileDir = plugins.path.dirname(filePathArg);
2023-11-24 19:15:41 +01:00
const ensureDirectoryExists = async () => {
while (true) {
try {
// Check if the directory exists
await plugins.smartpromise.fromCallback((cb) =>
plugins.fs.access(fileDir, plugins.fs.constants.R_OK, cb)
);
break; // Exit the loop if the directory exists
} catch (err) {
if (Date.now() - startTime > timeoutMs) {
reject(new Error(`Timeout waiting for directory to exist: ${fileDir}`));
return;
}
// Wait and retry
await plugins.smartdelay.delayFor(500);
}
}
};
2023-11-24 19:15:41 +01:00
const checkFileStability = async () => {
try {
const stats = await plugins.smartpromise.fromCallback<plugins.fs.Stats>((cb) =>
plugins.fs.stat(filePathArg, cb)
);
if (stats.size === lastFileSize) {
2023-11-24 19:15:41 +01:00
fileIsStable = true;
} else {
lastFileSize = stats.size;
fileIsStable = false;
}
} catch (err) {
if (err.code !== 'ENOENT') {
throw err; // Only ignore ENOENT (file not found) errors
2023-11-24 19:15:41 +01:00
}
2023-11-07 14:09:48 +01:00
}
};
// Ensure the directory exists before setting up the watcher
await ensureDirectoryExists();
const watcher = plugins.fs.watch(filePathArg, { persistent: true }, async () => {
if (!fileIsStable) {
await checkFileStability();
2023-11-07 14:09:48 +01:00
}
});
watcher.on('error', (error) => {
watcher.close();
reject(error);
});
2023-11-24 19:15:41 +01:00
try {
while (!fileIsStable) {
// Check for timeout
if (Date.now() - startTime > timeoutMs) {
watcher.close();
reject(new Error(`Timeout waiting for file to be ready: ${filePathArg}`));
return;
}
// Check file stability
await checkFileStability();
if (!fileIsStable) {
await plugins.smartdelay.delayFor(1000); // Polling interval
}
2023-11-24 19:15:41 +01:00
}
watcher.close();
resolve();
} catch (err) {
watcher.close();
reject(err);
2023-11-24 19:15:41 +01:00
}
2023-11-07 14:09:48 +01:00
});
2021-12-20 15:11:21 +01:00
};
2023-11-07 14:09:48 +01:00
2024-04-02 20:53:02 +02:00
/**
* writes string or Smartfile to disk.
* @param fileArg
* @param fileNameArg
* @param fileBaseArg
*/
export let toFs = async (
fileContentArg: string | Buffer | SmartFile | StreamFile,
2024-04-14 13:17:46 +02:00
filePathArg: string,
2024-04-02 20:53:02 +02:00
optionsArg: {
respectRelative?: boolean;
} = {}
) => {
const done = plugins.smartpromise.defer();
// check args
if (!fileContentArg || !filePathArg) {
throw new Error('expected valid arguments');
}
// prepare actual write action
let fileContent: string | Buffer;
let fileEncoding: 'utf8' | 'binary' = 'utf8';
let filePath: string = filePathArg;
// handle Smartfile
if (fileContentArg instanceof SmartFile) {
fileContent = fileContentArg.contentBuffer;
// handle options
if (optionsArg.respectRelative) {
filePath = plugins.path.join(filePath, fileContentArg.path);
}
} else if (Buffer.isBuffer(fileContentArg)) {
fileContent = fileContentArg;
fileEncoding = 'binary';
} else if (typeof fileContentArg === 'string') {
fileContent = fileContentArg;
} else {
throw new Error('fileContent is neither string nor Smartfile');
}
await ensureDir(plugins.path.parse(filePath).dir);
plugins.fsExtra.writeFile(filePath, fileContent, { encoding: fileEncoding }, done.resolve);
return await done.promise;
};
2023-11-07 14:09:48 +01:00
2024-06-06 23:33:35 +02:00
export const stat = async (filePathArg: string) => {
return plugins.fsPromises.stat(filePathArg);
};