197 lines
7.7 KiB
JavaScript
197 lines
7.7 KiB
JavaScript
![]() |
/* IMPORT */
|
||
|
import path from 'node:path';
|
||
|
import fs from 'stubborn-fs';
|
||
|
import { DEFAULT_ENCODING, DEFAULT_FILE_MODE, DEFAULT_FOLDER_MODE, DEFAULT_READ_OPTIONS, DEFAULT_WRITE_OPTIONS, DEFAULT_USER_UID, DEFAULT_USER_GID, DEFAULT_TIMEOUT_ASYNC, DEFAULT_TIMEOUT_SYNC, IS_POSIX } from './constants.js';
|
||
|
import { isException, isFunction, isString, isUndefined } from './utils/lang.js';
|
||
|
import Scheduler from './utils/scheduler.js';
|
||
|
import Temp from './utils/temp.js';
|
||
|
function readFile(filePath, options = DEFAULT_READ_OPTIONS) {
|
||
|
if (isString(options))
|
||
|
return readFile(filePath, { encoding: options });
|
||
|
const timeout = Date.now() + ((options.timeout ?? DEFAULT_TIMEOUT_ASYNC) || -1);
|
||
|
return fs.retry.readFile(timeout)(filePath, options);
|
||
|
}
|
||
|
function readFileSync(filePath, options = DEFAULT_READ_OPTIONS) {
|
||
|
if (isString(options))
|
||
|
return readFileSync(filePath, { encoding: options });
|
||
|
const timeout = Date.now() + ((options.timeout ?? DEFAULT_TIMEOUT_SYNC) || -1);
|
||
|
return fs.retry.readFileSync(timeout)(filePath, options);
|
||
|
}
|
||
|
function writeFile(filePath, data, options, callback) {
|
||
|
if (isFunction(options))
|
||
|
return writeFile(filePath, data, DEFAULT_WRITE_OPTIONS, options);
|
||
|
const promise = writeFileAsync(filePath, data, options);
|
||
|
if (callback)
|
||
|
promise.then(callback, callback);
|
||
|
return promise;
|
||
|
}
|
||
|
async function writeFileAsync(filePath, data, options = DEFAULT_WRITE_OPTIONS) {
|
||
|
if (isString(options))
|
||
|
return writeFileAsync(filePath, data, { encoding: options });
|
||
|
const timeout = Date.now() + ((options.timeout ?? DEFAULT_TIMEOUT_ASYNC) || -1);
|
||
|
let schedulerCustomDisposer = null;
|
||
|
let schedulerDisposer = null;
|
||
|
let tempDisposer = null;
|
||
|
let tempPath = null;
|
||
|
let fd = null;
|
||
|
try {
|
||
|
if (options.schedule)
|
||
|
schedulerCustomDisposer = await options.schedule(filePath);
|
||
|
schedulerDisposer = await Scheduler.schedule(filePath);
|
||
|
const filePathReal = await fs.attempt.realpath(filePath);
|
||
|
const filePathExists = !!filePathReal;
|
||
|
filePath = filePathReal || filePath;
|
||
|
[tempPath, tempDisposer] = Temp.get(filePath, options.tmpCreate || Temp.create, !(options.tmpPurge === false));
|
||
|
const useStatChown = IS_POSIX && isUndefined(options.chown);
|
||
|
const useStatMode = isUndefined(options.mode);
|
||
|
if (filePathExists && (useStatChown || useStatMode)) {
|
||
|
const stats = await fs.attempt.stat(filePath);
|
||
|
if (stats) {
|
||
|
options = { ...options };
|
||
|
if (useStatChown) {
|
||
|
options.chown = { uid: stats.uid, gid: stats.gid };
|
||
|
}
|
||
|
if (useStatMode) {
|
||
|
options.mode = stats.mode;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (!filePathExists) {
|
||
|
const parentPath = path.dirname(filePath);
|
||
|
await fs.attempt.mkdir(parentPath, {
|
||
|
mode: DEFAULT_FOLDER_MODE,
|
||
|
recursive: true
|
||
|
});
|
||
|
}
|
||
|
fd = await fs.retry.open(timeout)(tempPath, 'w', options.mode || DEFAULT_FILE_MODE);
|
||
|
if (options.tmpCreated) {
|
||
|
options.tmpCreated(tempPath);
|
||
|
}
|
||
|
if (isString(data)) {
|
||
|
await fs.retry.write(timeout)(fd, data, 0, options.encoding || DEFAULT_ENCODING);
|
||
|
}
|
||
|
else if (!isUndefined(data)) {
|
||
|
await fs.retry.write(timeout)(fd, data, 0, data.length, 0);
|
||
|
}
|
||
|
if (options.fsync !== false) {
|
||
|
if (options.fsyncWait !== false) {
|
||
|
await fs.retry.fsync(timeout)(fd);
|
||
|
}
|
||
|
else {
|
||
|
fs.attempt.fsync(fd);
|
||
|
}
|
||
|
}
|
||
|
await fs.retry.close(timeout)(fd);
|
||
|
fd = null;
|
||
|
if (options.chown && (options.chown.uid !== DEFAULT_USER_UID || options.chown.gid !== DEFAULT_USER_GID)) {
|
||
|
await fs.attempt.chown(tempPath, options.chown.uid, options.chown.gid);
|
||
|
}
|
||
|
if (options.mode && options.mode !== DEFAULT_FILE_MODE) {
|
||
|
await fs.attempt.chmod(tempPath, options.mode);
|
||
|
}
|
||
|
try {
|
||
|
await fs.retry.rename(timeout)(tempPath, filePath);
|
||
|
}
|
||
|
catch (error) {
|
||
|
if (!isException(error))
|
||
|
throw error;
|
||
|
if (error.code !== 'ENAMETOOLONG')
|
||
|
throw error;
|
||
|
await fs.retry.rename(timeout)(tempPath, Temp.truncate(filePath));
|
||
|
}
|
||
|
tempDisposer();
|
||
|
tempPath = null;
|
||
|
}
|
||
|
finally {
|
||
|
if (fd)
|
||
|
await fs.attempt.close(fd);
|
||
|
if (tempPath)
|
||
|
Temp.purge(tempPath);
|
||
|
if (schedulerCustomDisposer)
|
||
|
schedulerCustomDisposer();
|
||
|
if (schedulerDisposer)
|
||
|
schedulerDisposer();
|
||
|
}
|
||
|
}
|
||
|
function writeFileSync(filePath, data, options = DEFAULT_WRITE_OPTIONS) {
|
||
|
if (isString(options))
|
||
|
return writeFileSync(filePath, data, { encoding: options });
|
||
|
const timeout = Date.now() + ((options.timeout ?? DEFAULT_TIMEOUT_SYNC) || -1);
|
||
|
let tempDisposer = null;
|
||
|
let tempPath = null;
|
||
|
let fd = null;
|
||
|
try {
|
||
|
const filePathReal = fs.attempt.realpathSync(filePath);
|
||
|
const filePathExists = !!filePathReal;
|
||
|
filePath = filePathReal || filePath;
|
||
|
[tempPath, tempDisposer] = Temp.get(filePath, options.tmpCreate || Temp.create, !(options.tmpPurge === false));
|
||
|
const useStatChown = IS_POSIX && isUndefined(options.chown);
|
||
|
const useStatMode = isUndefined(options.mode);
|
||
|
if (filePathExists && (useStatChown || useStatMode)) {
|
||
|
const stats = fs.attempt.statSync(filePath);
|
||
|
if (stats) {
|
||
|
options = { ...options };
|
||
|
if (useStatChown) {
|
||
|
options.chown = { uid: stats.uid, gid: stats.gid };
|
||
|
}
|
||
|
if (useStatMode) {
|
||
|
options.mode = stats.mode;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (!filePathExists) {
|
||
|
const parentPath = path.dirname(filePath);
|
||
|
fs.attempt.mkdirSync(parentPath, {
|
||
|
mode: DEFAULT_FOLDER_MODE,
|
||
|
recursive: true
|
||
|
});
|
||
|
}
|
||
|
fd = fs.retry.openSync(timeout)(tempPath, 'w', options.mode || DEFAULT_FILE_MODE);
|
||
|
if (options.tmpCreated) {
|
||
|
options.tmpCreated(tempPath);
|
||
|
}
|
||
|
if (isString(data)) {
|
||
|
fs.retry.writeSync(timeout)(fd, data, 0, options.encoding || DEFAULT_ENCODING);
|
||
|
}
|
||
|
else if (!isUndefined(data)) {
|
||
|
fs.retry.writeSync(timeout)(fd, data, 0, data.length, 0);
|
||
|
}
|
||
|
if (options.fsync !== false) {
|
||
|
if (options.fsyncWait !== false) {
|
||
|
fs.retry.fsyncSync(timeout)(fd);
|
||
|
}
|
||
|
else {
|
||
|
fs.attempt.fsync(fd);
|
||
|
}
|
||
|
}
|
||
|
fs.retry.closeSync(timeout)(fd);
|
||
|
fd = null;
|
||
|
if (options.chown && (options.chown.uid !== DEFAULT_USER_UID || options.chown.gid !== DEFAULT_USER_GID)) {
|
||
|
fs.attempt.chownSync(tempPath, options.chown.uid, options.chown.gid);
|
||
|
}
|
||
|
if (options.mode && options.mode !== DEFAULT_FILE_MODE) {
|
||
|
fs.attempt.chmodSync(tempPath, options.mode);
|
||
|
}
|
||
|
try {
|
||
|
fs.retry.renameSync(timeout)(tempPath, filePath);
|
||
|
}
|
||
|
catch (error) {
|
||
|
if (!isException(error))
|
||
|
throw error;
|
||
|
if (error.code !== 'ENAMETOOLONG')
|
||
|
throw error;
|
||
|
fs.retry.renameSync(timeout)(tempPath, Temp.truncate(filePath));
|
||
|
}
|
||
|
tempDisposer();
|
||
|
tempPath = null;
|
||
|
}
|
||
|
finally {
|
||
|
if (fd)
|
||
|
fs.attempt.closeSync(fd);
|
||
|
if (tempPath)
|
||
|
Temp.purge(tempPath);
|
||
|
}
|
||
|
}
|
||
|
/* EXPORT */
|
||
|
export { readFile, readFileSync, writeFile, writeFileSync };
|