mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-08 14:34:13 +08:00
358 lines
9.3 KiB
JavaScript
358 lines
9.3 KiB
JavaScript
const fs = require("fs").promises;
|
|
const path = require("path");
|
|
|
|
/**
|
|
* 文件读取操作
|
|
* @param {Object} config 配置对象
|
|
* @param {string} config.filePath 文件路径
|
|
* @param {string} config.encoding 编码方式
|
|
* @param {string} config.readMode 读取模式
|
|
* @param {string} config.flag 读取标志
|
|
* @param {number} [config.start] 起始位置
|
|
* @param {number} [config.length] 读取长度
|
|
* @returns {Promise<string|Buffer>} 文件内容
|
|
*/
|
|
async function read(config) {
|
|
const { filePath, encoding, readMode, flag, start, length } = config;
|
|
|
|
if (readMode === "all") {
|
|
return await fs.readFile(filePath, { encoding, flag });
|
|
} else if (
|
|
readMode === "start" &&
|
|
typeof start === "number" &&
|
|
typeof length === "number"
|
|
) {
|
|
// 指定位置读取
|
|
const fileHandle = await fs.open(filePath, flag || "r");
|
|
try {
|
|
const buffer = Buffer.alloc(length);
|
|
const { bytesRead } = await fileHandle.read(buffer, 0, length, start);
|
|
await fileHandle.close();
|
|
return encoding
|
|
? buffer.slice(0, bytesRead).toString(encoding)
|
|
: buffer.slice(0, bytesRead);
|
|
} catch (error) {
|
|
await fileHandle.close();
|
|
throw error;
|
|
}
|
|
} else if (readMode === "line") {
|
|
// 按行读取,暂时使用全部读取然后分行的方式
|
|
const content = await fs.readFile(filePath, {
|
|
encoding: encoding || "utf8",
|
|
flag,
|
|
});
|
|
return content.split(/\r?\n/);
|
|
} else {
|
|
// 默认使用全部读取
|
|
return await fs.readFile(filePath, { encoding, flag });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 文件写入操作
|
|
* @param {Object} config 配置对象
|
|
* @param {string} config.filePath 文件路径
|
|
* @param {string} config.content 写入内容
|
|
* @param {string} config.encoding 编码方式
|
|
* @param {string} config.flag 写入标志 ('w'=覆盖写入, 'a'=追加写入)
|
|
* @param {string|number} config.mode 文件权限 (例如: '666', '644', '755')
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async function write(config) {
|
|
const { filePath, content, encoding, flag = "w", mode } = config;
|
|
|
|
try {
|
|
// 确保目录存在
|
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
|
|
// 写入文件
|
|
const options = {
|
|
encoding,
|
|
flag,
|
|
mode: mode ? parseInt(mode, 8) : undefined,
|
|
};
|
|
|
|
await fs.writeFile(filePath, content, options);
|
|
} catch (error) {
|
|
if (error.code === "EPERM") {
|
|
throw new Error("没有写入权限");
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 文件删除操作
|
|
*/
|
|
async function remove(config) {
|
|
const { filePath, recursive, force, targetType } = config;
|
|
|
|
// 检查文件是否存在
|
|
try {
|
|
const stats = await fs.lstat(filePath);
|
|
|
|
// 检查目标类型
|
|
if (targetType === "file" && !stats.isFile()) {
|
|
throw new Error("目标不是文件");
|
|
}
|
|
if (targetType === "directory" && !stats.isDirectory()) {
|
|
throw new Error("目标不是目录");
|
|
}
|
|
|
|
// 执行删除操作
|
|
if (stats.isDirectory()) {
|
|
await fs.rm(filePath, { recursive, force });
|
|
} else {
|
|
await fs.unlink(filePath);
|
|
}
|
|
} catch (error) {
|
|
if (error.code === "ENOENT") {
|
|
if (!force) throw new Error("文件或目录不存在");
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 文件管理操作
|
|
*/
|
|
async function manage(config) {
|
|
const {
|
|
filePath,
|
|
manageOperation,
|
|
newPath,
|
|
mode,
|
|
uid,
|
|
gid,
|
|
recursive,
|
|
targetType,
|
|
} = config;
|
|
|
|
// 检查文件是否存在
|
|
const stats = await fs.lstat(filePath);
|
|
|
|
// 检查目标类型
|
|
if (targetType === "file" && !stats.isFile()) {
|
|
throw new Error("目标不是文件");
|
|
}
|
|
if (targetType === "directory" && !stats.isDirectory()) {
|
|
throw new Error("目标不是目录");
|
|
}
|
|
|
|
switch (manageOperation) {
|
|
case "rename":
|
|
// 确保目标目录存在
|
|
await fs.mkdir(path.dirname(newPath), { recursive: true });
|
|
await fs.rename(filePath, newPath);
|
|
break;
|
|
|
|
case "chmod":
|
|
if (recursive && stats.isDirectory()) {
|
|
const walk = async (dir) => {
|
|
const files = await fs.readdir(dir);
|
|
for (const file of files) {
|
|
const curPath = path.join(dir, file);
|
|
const stat = await fs.lstat(curPath);
|
|
await fs.chmod(curPath, parseInt(mode, 8));
|
|
if (stat.isDirectory()) {
|
|
await walk(curPath);
|
|
}
|
|
}
|
|
};
|
|
|
|
await fs.chmod(filePath, parseInt(mode, 8));
|
|
await walk(filePath);
|
|
} else {
|
|
await fs.chmod(filePath, parseInt(mode, 8));
|
|
}
|
|
break;
|
|
|
|
case "chown":
|
|
if (recursive && stats.isDirectory()) {
|
|
await fs.chown(filePath, uid, gid);
|
|
const walk = async (dir) => {
|
|
const files = await fs.readdir(dir);
|
|
for (const file of files) {
|
|
const curPath = path.join(dir, file);
|
|
const stat = await fs.lstat(curPath);
|
|
await fs.chown(curPath, uid, gid);
|
|
if (stat.isDirectory()) {
|
|
await walk(curPath);
|
|
}
|
|
}
|
|
};
|
|
|
|
await fs.chown(filePath, uid, gid);
|
|
await walk(filePath);
|
|
} else {
|
|
await fs.chown(filePath, uid, gid);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw new Error(`不支持的操作类型: ${manageOperation}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 列出目录内容
|
|
* @param {Object} config 配置对象
|
|
* @param {string} config.filePath 目录路径
|
|
* @param {boolean} config.recursive 是否递归列出子目录
|
|
* @param {boolean} config.showHidden 是否显示隐藏文件
|
|
* @returns {Promise<Array>} 文件列表
|
|
*/
|
|
async function list(config) {
|
|
const { filePath, recursive, showHidden } = config;
|
|
|
|
if (recursive) {
|
|
const result = [];
|
|
const walk = async (dir) => {
|
|
const files = await fs.readdir(dir);
|
|
for (const file of files) {
|
|
if (!showHidden && file.startsWith(".")) continue;
|
|
const curPath = path.join(dir, file);
|
|
const stat = await fs.lstat(curPath);
|
|
result.push({
|
|
path: curPath,
|
|
isDirectory: stat.isDirectory(),
|
|
isFile: stat.isFile(),
|
|
isSymbolicLink: stat.isSymbolicLink(),
|
|
});
|
|
if (stat.isDirectory()) {
|
|
await walk(curPath);
|
|
}
|
|
}
|
|
};
|
|
await walk(filePath);
|
|
return result;
|
|
} else {
|
|
const files = await fs.readdir(filePath);
|
|
return Promise.all(
|
|
files
|
|
.filter((file) => showHidden || !file.startsWith("."))
|
|
.map(async (file) => {
|
|
const curPath = path.join(filePath, file);
|
|
const stat = await fs.lstat(curPath);
|
|
return {
|
|
path: curPath,
|
|
isDirectory: stat.isDirectory(),
|
|
isFile: stat.isFile(),
|
|
isSymbolicLink: stat.isSymbolicLink(),
|
|
};
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取文件或目录状态
|
|
* @param {Object} config 配置对象
|
|
* @param {string} config.filePath 路径
|
|
* @param {string} config.targetType 目标类型
|
|
* @param {string} config.statMode 检查类型
|
|
* @param {boolean} [config.followSymlinks] 是否跟随符号链接
|
|
* @returns {Promise<Object>} 状态信息
|
|
*/
|
|
async function stat(config) {
|
|
const { filePath, targetType, statMode, followSymlinks } = config;
|
|
|
|
try {
|
|
const statFn = followSymlinks ? fs.stat : fs.lstat;
|
|
const stats = await statFn(filePath);
|
|
|
|
// 检查目标类型是否匹配
|
|
if (targetType === "file" && !stats.isFile()) {
|
|
throw new Error("目标不是文件");
|
|
}
|
|
if (targetType === "directory" && !stats.isDirectory()) {
|
|
throw new Error("目标不是目录");
|
|
}
|
|
|
|
// 根据检查类型返回不同的信息
|
|
if (statMode === "exists") {
|
|
return {
|
|
exists: true,
|
|
isFile: stats.isFile(),
|
|
isDirectory: stats.isDirectory(),
|
|
};
|
|
} else if (statMode === "status") {
|
|
return {
|
|
exists: true,
|
|
isFile: stats.isFile(),
|
|
isDirectory: stats.isDirectory(),
|
|
isSymbolicLink: stats.isSymbolicLink(),
|
|
size: stats.size,
|
|
mode: stats.mode,
|
|
uid: stats.uid,
|
|
gid: stats.gid,
|
|
accessTime: stats.atime,
|
|
modifyTime: stats.mtime,
|
|
changeTime: stats.ctime,
|
|
birthTime: stats.birthtime,
|
|
};
|
|
} else {
|
|
// 默认返回基本信息
|
|
return {
|
|
exists: true,
|
|
isFile: stats.isFile(),
|
|
isDirectory: stats.isDirectory(),
|
|
isSymbolicLink: stats.isSymbolicLink(),
|
|
};
|
|
}
|
|
} catch (error) {
|
|
if (error.code === "ENOENT") {
|
|
return {
|
|
exists: false,
|
|
...(statMode === "exists" && {
|
|
isFile: false,
|
|
isDirectory: false,
|
|
}),
|
|
};
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 统一的文件操作入口
|
|
*/
|
|
async function operation(config) {
|
|
if (!config || typeof config !== "object") {
|
|
throw new Error("配置参数必须是一个对象");
|
|
}
|
|
|
|
const { operation } = config;
|
|
if (!operation) {
|
|
throw new Error("缺少必要的 operation 参数");
|
|
}
|
|
|
|
switch (operation) {
|
|
case "read":
|
|
return await read(config);
|
|
case "write":
|
|
return await write(config);
|
|
case "list":
|
|
return await list(config);
|
|
case "stat":
|
|
return await stat(config);
|
|
case "delete":
|
|
return await remove(config);
|
|
case "manage":
|
|
return await manage(config);
|
|
default:
|
|
throw new Error(`不支持的操作类型: ${operation}`);
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
read,
|
|
write,
|
|
list,
|
|
stat,
|
|
remove,
|
|
manage,
|
|
operation,
|
|
};
|