You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1151 lines
34 KiB

import * as fspath from './Path';
import { OsFileInterface } from './FIleInterface';
import { SystemOptions } from '../type/type';
import { InitSystemFile, InitUserFile } from './SystemFileConfig';
import { createInitFile } from './createInitFile';
import { OsFileMode } from './FileMode';
import JSZip from "jszip";
type DateLike = Date | string | number;
// Os文件模式枚举
class OsFileInfo {
// 是否是文件
isFile = true;
// 是否是目录
isDirectory = false;
// 是否是符号链接
isSymlink = false;
// 文件大小
size = 0;
// 最后一次修改此文件的时间戳
mtime: DateLike = new Date();
// 最后一次访问此文件的时间戳
atime: DateLike = new Date();
// 此文件创建时间的时间戳
birthtime: DateLike = new Date();
// 文件权限
mode = 0o777;
// 设备编号
rdev = 0;
// 构造函数
constructor(
// 是否是文件
isFile?: boolean,
// 是否是目录
isDirectory?: boolean,
// 是否是符号链接
isSymlink?: boolean,
// 文件大小
size?: number,
// 最后一次修改此文件的时间戳
mtime?: DateLike,
// 最后一次访问此文件的时间戳
atime?: DateLike,
// 此文件创建时间的时间戳
birthtime?: DateLike,
// 文件权限
mode?: OsFileMode,
// 设备编号
rdev?: number
) {
// 设置是否是文件
if (isFile !== undefined) {
this.isFile = isFile;
}
// 设置是否是目录
if (isDirectory !== undefined) {
this.isDirectory = isDirectory;
}
// 设置是否是符号链接
if (isSymlink !== undefined) {
this.isSymlink = isSymlink;
}
// 设置文件大小
if (size !== undefined) {
this.size = size;
}
// 设置最后一次修改此文件的时间戳
if (mtime !== undefined) {
this.mtime = mtime;
}
// 设置最后一次访问此文件的时间戳
if (atime !== undefined) {
this.atime = atime;
}
// 设置此文件创建时间的时间戳
if (birthtime !== undefined) {
this.birthtime = birthtime;
}
// 设置文件权限
if (mode !== undefined) {
this.mode = mode;
}
// 设置设备编号
if (rdev !== undefined) {
this.rdev = rdev;
}
}
}
/**
* OsFile 类是继承自 OsFileInfo 的一个类。
*/
class OsFile extends OsFileInfo {
name = ''; // 文件名
path: string; // 文件路径
oldPath?: string; // 旧的文件路径
parentPath: string; // 父目录路径
content: any; // 文件内容
ext?: string; // 文件扩展名
title?: string; // 文件名(不包含扩展名)
id?: number; // 文件ID(可选)
isSys?: number; // 文件是否是系统文件(可选)
isShare?: boolean; // 文件是否为共享文件
isPwd?: boolean; //文件是否上锁
titleName?: string; // 文件原本路径
/**
* OsFile 类的构造函数。
* @param path 文件路径
* @param content 文件内容
* @param info 文件信息
* @param id 文件ID(可选)
*/
constructor(
path: string,
content: any,
info: Partial<OsFileInfo>,
id?: number
) {
if (info.isFile) {
info.isDirectory = false;
info.isSymlink = false;
}
if (info.isDirectory) {
info.isFile = false;
info.isSymlink = false;
}
if (info.isSymlink) {
info.isFile = false;
info.isDirectory = false;
}
super(info.isFile, info.isDirectory, info.isSymlink, info.size, info.mtime, info.atime, info.birthtime);
this.path = path; // 设置文件路径
this.parentPath = fspath.dirname(path); // 获取文件所在目录路径
this.name = fspath.basename(path); // 获取文件名
if (!this.oldPath) {
this.oldPath = this.path; // 设置旧的文件路径
}
if (info.isFile) {
const titleArr = this.name.split('.');
this.ext = titleArr.pop(); // 获取文件扩展名
this.title = titleArr.join("."); // 获取文件名(不包含扩展名)
}
this.content = content; // 设置文件内容
// this.icon = icon; // 文件图标
// this.type = type; // 文件类型
this.id = id; // 设置文件ID
if (id === undefined) {
delete this.id; // 如果文件ID为空,则删除该属性
}
if (this.isSys === undefined) {
this.isSys = 1; // 如果文件系统属性为空,则设置为1(系统文件)
}
}
}
export type OsFileWithoutContent = Omit<OsFile, 'content'>;
/**
* Os文件系统类
*
* @implements {OsFileInterface}
*/
class OsFileSystem implements OsFileInterface {
/**
* 私有的数据库实例
* @type {IDBDatabase}
*/
private db!: IDBDatabase;
/**
* 当数据库准备就绪时的回调函数
* @type {((value: OsFileSystem) => void) | null}
*/
private _ready: ((value: OsFileSystem) => void) | null = null;
/**
* 监听路径和内容变化的回调函数映射表
* @type {Map<RegExp, (path: string, content: string) => void>}
*/
private _watchMap: Map<RegExp, (path: string, content: string) => void> = new Map();
/**
* 文件卷映射表
* @type {Map<string, OsFileInterface>}
*/
private volumeMap: Map<string, OsFileInterface> = new Map();
/**
* 当打开数据库失败时触发的错误处理函数
* @param e - 错误对象
*/
onerror: (e: any) => void = () => {
console.error('Failed to open database');
};
/**
* 构造函数
* @param {string} rootPath - 根路径,默认为'/'
* @param {string} id - id,默认为空字符串
*/
constructor(rootPath = '/', id = '') {
// 打开IndexedDB数据库
const request = window.indexedDB.open('FileSystemDB' + id, 1);
// 打开失败时的错误处理
request.onerror = () => {
console.error('Failed to open database');
};
// 打开成功时的处理
request.onsuccess = () => {
this.db = request.result;
this._ready?.(this);
};
// 当数据库升级时的处理
request.onupgradeneeded = () => {
this.db = request.result;
// 创建对象存储空间'files'
const objectStore = this.db.createObjectStore('files', { keyPath: 'id', autoIncrement: true });
// 创建索引'parentPath'
objectStore.createIndex('parentPath', 'parentPath');
// 创建索引'path',设置唯一性
objectStore.createIndex('path', 'path', { unique: true });
// 创建索引'name'
objectStore.createIndex('name', 'name');
// 创建根目录
const rootDir = new OsFile(rootPath, '', {
mode: 0o111,
isDirectory: true,
});
rootDir.parentPath = rootPath === '/' ? '' : fspath.dirname(rootPath);
// 将根目录添加到对象存储空间
objectStore.add(rootDir);
};
}
/**
* 初始化文件系统
* @param option 系统选项
* @returns 返回文件系统实例
*/
async initFileSystem(option: SystemOptions) {
await this.whenReady();
await this.mkdir('/C');
await this.chmod('/C', OsFileMode.Read);
await createInitFile(this, option.initFile || InitUserFile, option.userLocation);
await createInitFile(this, option.initFile || InitSystemFile, option.systemLocation);
await this.mkdir('/D');
await this.chmod('/D', OsFileMode.Read);
await this.mkdir('/E');
await this.chmod('/E', OsFileMode.Read);
await this.mkdir('/B');
await this.chmod('/B', OsFileMode.Read);
return this;
}
on(_: 'error', func: (e: any) => void) {
this.onerror = func;
}
/**
* 序列化文件系统
* @returns {Promise<any>} 返回一个Promise对象,包含文件系统的数据
*/
serializeFileSystem() {
return new Promise((resolve, reject) => {
// 创建一个只读事务
const transaction = this.db.transaction('files', 'readonly');
// 获取文件对象存储
const objectStore = transaction.objectStore('files');
// 获取所有文件数据的请求
const request = objectStore.getAll();
// 请求失败时的处理函数
request.onerror = () => {
reject('Failed to read file');
};
// 请求成功时的处理函数
request.onsuccess = () => {
resolve(request.result);
};
});
}
/**
* 反序列化文件系统
* @param files 要添加到文件系统的文件数组
* @returns 返回一个Promise对象,成功时无返回值,失败时返回错误信息
*/
deserializeFileSystem(files: OsFile[]) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction('files', 'readwrite');
const objectStore = transaction.objectStore('files');
const request = objectStore.clear();
request.onerror = () => {
reject('Failed to clear file');
};
request.onsuccess = () => {
files.forEach((file) => {
objectStore.add(file);
});
resolve(void 0);
};
});
}
/**
* 当准备就绪时调用的函数,返回一个Promise对象,该对象在OsFileSystem实例准备就绪后会被resolve。
* 如果OsFileSystem实例已经准备就绪,则直接返回该实例。
*
* @returns {Promise<OsFileSystem>} 返回一个Promise对象,该对象在OsFileSystem实例准备就绪后会被resolve。
*/
whenReady(): Promise<OsFileSystem> {
if (this.db) {
return Promise.resolve(this);
}
return new Promise<OsFileSystem>((resolve) => {
this._ready = resolve;
});
}
/**
* 注册观察者
* @param path - 观察的路径,使用正则表达式表示
* @param callback - 当路径发生变化时调用的回调函数,接收路径和内容作为参数
*/
registerWatcher(path: RegExp, callback: (path: string, content: string) => void) {
this._watchMap.set(path, callback);
}
/**
* 提交文件变更观察
* @param {string} path - 文件路径
* @param {string} content - 文件内容
*/
commitWatch(path: string, content: any) {
// 遍历观察回调函数
this._watchMap.forEach((callback, reg) => {
// 如果文件路径匹配正则表达式
if (reg.test(path)) {
// 调用回调函数,传入文件路径和内容
callback(path, content);
}
});
}
/**
* 删除文件系统数据库
* @returns {Promise} 删除完成的Promise对象
*/
async removeFileSystem() {
window.indexedDB.deleteDatabase('FileSystemDB');
return Promise.resolve();
}
/**
* 挂载卷到指定路径
* @param path - 路径
* @param volume - 卷对象
*/
mountVolume(path: string, volume: OsFileInterface) {
this.volumeMap.set(path, volume);
}
/**
* 检查给定路径下的卷文件
* @param path 要检查的路径
* @returns 卷文件或undefined
*/
checkVolumeChild(path: string): OsFileInterface | undefined {
let volume: OsFileInterface | undefined;
this.volumeMap.forEach((volumem, key) => {
if (fspath.dirname(key) === path) {
volume = volumem;
}
});
return volume;
}
/**
* 判断指定路径是否为卷的路径
* @param path
* @returns
*/
checkVolumePath(path: string): OsFileInterface | undefined {
if (this.volumeMap.has(path)) {
return this.volumeMap.get(path);
}
let volume: OsFileInterface | undefined;
this.volumeMap.forEach((volumem, key) => {
if (fspath.isChildPath(key, path)) {
volume = volumem;
}
});
return volume;
}
/**
* 使用对应的卷的文件系统
*/
beforeGuard<T extends keyof OsFileInterface>(
volume: OsFileInterface,
opt: T,
...args: Parameters<OsFileInterface[T]>
) {
return (volume[opt] as (...args: Parameters<OsFileInterface[T]>) => ReturnType<OsFileInterface[T]>)(
...args
);
}
/**
* 读取指定路径的文件内容
* @param path 文件路径
* @returns 文件内容
*/
async readFile(path: string): Promise<string | any> {
try {
const volume = this.checkVolumePath(path);
if (volume) {
return this.beforeGuard(volume, 'readFile', path);
}
const transaction = this.db.transaction('files', 'readonly');
const objectStore = transaction.objectStore('files');
const index = objectStore.index('path');
const range = IDBKeyRange.only(path);
const request = index.get(range);
return new Promise((resolve, reject) => {
request.onerror = () => {
reject('Failed to read file');
};
request.onsuccess = () => {
const file: OsFile = request.result;
resolve(file ? file.content : null);
};
});
} catch (e: any) {
this.onerror(e.toString());
return Promise.reject(e);
}
}
/**
* 写入文件 不存在则创建,存在则覆盖
* @param path 文件路径
* @param data 文件内容
* @param opt 可选参数
* @param opt.flag 写入模式,可选值为 'w' (覆盖写入),'a' (追加写入),'wx' (排他写入)
* @returns Promise
*/
async writeFile(
path: string,
data: any,
opt?: {
flag?: 'w' | 'a' | 'wx';
}
): Promise<void> {
const volume = this.checkVolumePath(path);
if (volume) {
return this.beforeGuard(volume, 'writeFile', path, data, opt);
}
const parentPath = fspath.dirname(path);
// 判断文件是否存在
const exists = await this.exists(parentPath);
if (!exists) {
this.onerror('无法写入一个不存在的路径上的文件:' + path);
return Promise.reject('无法写入一个不存在的路径上的文件:' + path);
}
const transaction = this.db.transaction('files', 'readwrite');
const objectStore = transaction.objectStore('files');
const stats: OsFile | null = await new Promise((resolve, reject) => {
objectStore.index('path').openCursor(IDBKeyRange.only(path)).onsuccess = (event: any) => {
const cursor: IDBCursorWithValue = event.target.result;
if (cursor) {
const file: OsFile = cursor.value;
if (file.isDirectory) {
reject('无法写入一个目录');
} else {
resolve(file);
}
} else {
resolve(null);
}
};
});
if (!stats) {
const request = objectStore.add(
new OsFile(path, data, {
isFile: true,
size: (typeof data === 'string' ? data.length : data.byteLength),
})
);
return new Promise((resolve, reject) => {
request.onerror = () => {
this.onerror('写入文件失败');
reject('写入文件失败');
};
request.onsuccess = () => {
this.commitWatch(path, data);
resolve();
};
});
} else {
if (opt?.flag === 'wx') {
// 排他模式
return Promise.resolve();
}
if (opt?.flag === 'a') {
// 追加模式
data = stats.content + data;
}
const request = objectStore.put({
...stats,
content: data,
size: (typeof data === 'string' ? data.length : data.byteLength),
mtime: new Date(),
});
return new Promise((resolve, reject) => {
request.onerror = () => {
this.onerror('写入文件失败');
reject('写入文件失败');
};
request.onsuccess = () => {
this.commitWatch(path, data);
resolve();
};
});
}
}
/**
* 在指定路径的文件末尾追加内容。
* @param path 文件路径
* @param content 要追加的内容
* @returns 返回一个Promise,表示操作的异步结果
*/
async appendFile(path: string, content: string): Promise<void> {
const volume = this.checkVolumePath(path);
if (volume) {
return this.beforeGuard(volume, 'appendFile', path, content);
}
const transaction = this.db.transaction('files', 'readwrite');
const objectStore = transaction.objectStore('files');
const index = objectStore.index('path');
const range = IDBKeyRange.only(path);
const request = index.get(range);
return new Promise((resolve, reject) => {
request.onerror = () => {
this.onerror('Failed to write file');
reject('Failed to read file');
};
request.onsuccess = () => {
const file: OsFile = request.result;
if (file) {
file.content += content;
file.size = file.content.length;
file.mtime = new Date();
const request = objectStore.put(file);
request.onerror = () => {
this.onerror('Failed to write file');
reject('Failed to write file');
};
request.onsuccess = () => {
this.commitWatch(path, file.content);
resolve();
};
} else {
this.onerror('File not found');
reject('File not found');
}
};
});
}
/**
* 读取指定路径下的所有文件和文件夹
* @param fpath 目录路径
* @returns 文件和文件夹列表
*/
async readdir(fpath: string): Promise<OsFileWithoutContent[]> {
const path = fspath.resolve(fpath);
const volume = this.checkVolumePath(path);
if (volume) {
return this.beforeGuard(volume, 'readdir', path);
}
const volume2 = this.checkVolumeChild(path);
let vol: OsFileWithoutContent[] = [];
if (volume2) {
try {
vol = await this.beforeGuard(volume2, 'readdir', path);
} catch {
this.onerror('Failed to read volume directory:' + path);
}
}
const transaction = this.db.transaction('files', 'readonly');
const objectStore = transaction.objectStore('files');
const index = objectStore.index('parentPath');
const range = IDBKeyRange.only(path);
//console.log(range)
const request = index.getAll(range);
//console.log(request)
return new Promise((resolve, reject) => {
request.onerror = () => {
this.onerror('Failed to read directory');
reject('Failed to read directory');
};
request.onsuccess = () => {
const files = request.result;
//console.log(files)
resolve([...files, ...vol]);
};
});
}
async exists(path: string): Promise<boolean> {
const volume = this.checkVolumePath(path);
if (volume) {
try {
return this.beforeGuard(volume, 'exists', path);
} catch {
this.onerror('Failed to read volume directory:' + path);
}
}
try {
const transaction = this.db.transaction('files', 'readonly');
const objectStore = transaction.objectStore('files');
const index = objectStore.index('path');
const range = IDBKeyRange.only(path);
const request = index.getAll(range);
return new Promise((resolve, reject) => {
request.onerror = () => {
this.onerror('Failed to read file');
reject('Failed to read file');
};
request.onsuccess = () => {
const fileArray: OsFile[] = request.result;
resolve(fileArray.length ? true : false);
};
});
} catch (e) {
return false;
}
}
/**
* 获取文件信息
* @param path 文件路径
* @returns 返回文件信息的Promise
*/
async stat(path: string): Promise<OsFileWithoutContent | null> {
const volume = this.checkVolumePath(path);
if (volume) {
try {
return this.beforeGuard(volume, 'stat', path);
} catch {
this.onerror('Failed to read volume directory:' + path);
}
}
const transaction = this.db.transaction('files', 'readonly');
const objectStore = transaction.objectStore('files');
const index = objectStore.index('path');
const range = IDBKeyRange.only(path);
const request = index.get(range);
return new Promise((resolve, reject) => {
request.onerror = () => {
this.onerror('Failed to read file');
reject('Failed to read file');
};
request.onsuccess = () => {
const file: OsFile = request.result;
resolve(file);
};
});
}
/**
* 删除指定路径的文件
* @param path 文件路径
*/
async unlink(path: string): Promise<void> {
const volume = this.checkVolumePath(path);
if (volume) {
try {
return this.beforeGuard(volume, 'unlink', path);
} catch {
this.onerror('Failed to unlink volume file:' + path);
}
}
const transaction = this.db.transaction('files', 'readwrite');
const objectStore = transaction.objectStore('files');
const index = objectStore.index('path');
const range = IDBKeyRange.only(path);
const request = index.get(range);
return new Promise((resolve, reject) => {
request.onerror = () => {
this.onerror('Failed to delete file');
reject('Failed to delete file');
};
request.onsuccess = () => {
const file: OsFile = request.result;
if (file) {
if (file.isDirectory) {
reject('Cannot delete a directory');
} else {
objectStore.delete(request.result.id);
this.commitWatch(path, file.content);
resolve();
}
} else {
reject('File not found');
}
};
});
}
/**
* 深度遍历文件系统并重命名文件或目录
* @param vfile - 要重命名的文件或目录对象
* @param objectStore - IDBObjectStore 对象存储
* @param newPath - 新路径
*/
private async dfsRename(vfile: OsFile, objectStore: IDBObjectStore, newPath: string) {
if (vfile.isDirectory) {
// 对于目录,遍历其所有父目录的文件和子目录
objectStore.index('parentPath').openCursor(IDBKeyRange.only(vfile.path)).onsuccess = (event: any) => {
const cursor: IDBCursorWithValue = event.target.result;
if (cursor) {
const tempfile = cursor.value;
const tempNewPath = fspath.join(newPath, fspath.basename(tempfile.path));
// 递归调用 dfsRename 函数重命名文件或目录
this.dfsRename(tempfile, objectStore, tempNewPath);
cursor.continue();
}
};
}
const vParentPath = fspath.dirname(newPath);
vfile.oldPath = vfile.path;
vfile.path = newPath;
vfile.parentPath = vParentPath;
vfile.mtime = new Date();
// 更新文件或目录信息到 objectStore
objectStore.put(vfile);
}
/**
* 重命名文件或目录
* @param path - 要重命名的文件或目录的路径
* @param newPath - 新的文件或目录路径
* @returns 返回一个Promise,当重命名成功时,Promise将解析为undefined
* @throws 当重命名失败时,Promise将被拒绝并抛出错误
*/
async rename(path: string, newPath: string): Promise<void> {
const volume = this.checkVolumePath(path);
const volume2 = this.checkVolumePath(newPath);
if (!!volume && !!volume2) {
return this.beforeGuard(volume, 'rename', path, newPath);
} else if ((!!volume && !volume2) || (!volume && !!volume2)) {
this.onerror('Cannot rename between volumes');
return Promise.reject('Cannot rename between volumes');
}
// this.beforeGuard('rename', path, newPath);
// 不能重命名为子路径
// /C/Users /C/Users/Desktop/Users
if (path === newPath) {
return Promise.resolve();
}
if (fspath.isChildPath(path, newPath)) {
this.onerror('Cannot rename to child path');
return Promise.reject('Cannot rename to child path');
}
// if (newPath.startsWith(path)) {//bug
// return Promise.reject('Cannot rename to child path');
// }
const transaction = this.db.transaction('files', 'readwrite');
const objectStore = transaction.objectStore('files');
const index = objectStore.index('path');
const range = IDBKeyRange.only(path);
const request = index.get(range);
return new Promise((resolve, reject) => {
request.onerror = () => {
this.onerror('Failed to read file');
reject('Failed to read file');
};
request.onsuccess = () => {
const file: OsFile = request.result;
if (file) {
this.dfsRename(file, objectStore, newPath);
this.commitWatch(path, file.content);
}
resolve();
};
});
}
/**
* 递归删除指定目录及其子目录
* @param vfile 要删除的目录文件对象
* @param objectStore 对象存储
* @returns 返回一个Promise对象,表示删除操作是否成功
*/
private async dfsRmdir(vfile: OsFile, objectStore: IDBObjectStore) {
if (vfile.mode) {
if (vfile.mode <= 0o111) {
this.onerror('Cannot delete a readonly file');
return Promise.reject('Cannot delete a readonly file');
}
}
if (vfile.isDirectory) {
// 获取指定目录的所有父目录
objectStore.index('parentPath').openCursor(IDBKeyRange.only(vfile.path)).onsuccess = (event: any) => {
const cursor: IDBCursorWithValue = event.target.result;
if (cursor) {
const tempfile = cursor.value;
// 递归删除父目录及其子目录
this.dfsRmdir(tempfile, objectStore);
cursor.continue();
}
};
}
// 删除指定目录的所有文件
objectStore.index('path').openCursor(IDBKeyRange.only(vfile.path)).onsuccess = (event: any) => {
const cursor: IDBCursorWithValue = event.target.result;
if (cursor) {
objectStore.delete(cursor.value.id);
cursor.continue();
}
};
// 提交对指定目录的监视
this.commitWatch(vfile.path, vfile.content);
}
/**
* 删除指定路径的文件夹及其内容
* @param path 文件夹路径
*/
async rmdir(path: string): Promise<void> {
const volume = this.checkVolumePath(path);
if (volume) {
return this.beforeGuard(volume, 'rmdir', path);
}
const transaction = this.db.transaction('files', 'readwrite');
const objectStore = transaction.objectStore('files');
const index = objectStore.index('path');
const range = IDBKeyRange.only(path);
const request = index.get(range);
return new Promise((resolve, reject) => {
request.onerror = () => {
this.onerror('Failed to read file');
reject('Failed to read file');
};
request.onsuccess = () => {
const file: OsFile = request.result;
if (file) {
this.dfsRmdir(file, objectStore);
}
this.commitWatch(path, file.content);
resolve();
};
});
}
/**
* 创建新的文件夹
* @param path 文件夹路径
*/
async mkdir(path: string): Promise<void> {
const volume = this.checkVolumePath(path);
if (volume) {
return this.beforeGuard(volume, 'mkdir', path);
}
// 转换路径
const transedPath = fspath.transformPath(path);
// 获取父路径
let parentPath = fspath.dirname(transedPath);
// 如果父路径为空,设置为根路径
if (parentPath === '') parentPath = '/';
// 判断文件是否存在
const exists = await this.exists(parentPath);
// 如果文件不存在,则抛出错误
if (!exists && path != parentPath) {
this.onerror('Cannot create directory to a non-exist path:' + parentPath);
return Promise.reject('Cannot create directory to a non-exist path:' + parentPath);
}
// 检查目录是否已存在
const res = await this.exists(transedPath);
if (res) {
// 目录已存在,无需操作
// console.error("Directory already exists");
return Promise.resolve();
}
// 开始数据库事务
const transaction = this.db.transaction('files', 'readwrite');
// 获取文件对象存储
const objectStore = transaction.objectStore('files');
// 添加新目录到文件对象存储
const request = objectStore.add(
new OsFile(transedPath, '', {
isDirectory: true,
})
);
return new Promise((resolve, reject) => {
request.onerror = () => {
this.onerror('Failed to create directory');
reject('Failed to create directory');
};
request.onsuccess = () => {
this.commitWatch(transedPath, '');
resolve();
};
});
}
/**
* 深度复制文件
* @param vfile - 虚拟文件对象
* @param objectStore - IDBObjectStore对象
* @param newPath - 新路径
*/
private async dfsCopFile(vfile: OsFile, objectStore: IDBObjectStore, newPath: string) {
if (vfile.isDirectory) {
// 获取所有父目录的文件
objectStore.index('parentPath').openCursor(IDBKeyRange.only(vfile.path)).onsuccess = (event: any) => {
const cursor: IDBCursorWithValue = event.target.result;
if (cursor) {
const tempfile = cursor.value;
const tempNewPath = fspath.join(newPath, fspath.basename(tempfile.path));
// 递归复制父目录下的所有文件
this.dfsCopFile(tempfile, objectStore, tempNewPath);
cursor.continue();
}
};
}
// 复制文件
const newFile = {
...vfile,
path: newPath,
parentPath: fspath.dirname(newPath),
mtime: new Date(),
};
delete newFile.id;
objectStore.put(newFile);
}
/**
* 复制文件
* @param src 文件源路径
* @param dest 文件目标路径
* @returns 返回一个Promise
*/
async copyFile(src: string, dest: string): Promise<void> {
const volume = this.checkVolumePath(src);
const volume2 = this.checkVolumePath(dest);
if (!!volume && !!volume2) {
return this.beforeGuard(volume, 'copyFile', src, dest);
} else if ((!!volume && !volume2) || (!volume && !!volume2)) {
/**
* 无法在卷之间复制文件
*/
this.onerror('Cannot copyFile between volumes');
return Promise.reject('Cannot copyFile between volumes');
}
const transaction = this.db.transaction('files', 'readwrite');
const objectStore = transaction.objectStore('files');
const index = objectStore.index('path');
const range = IDBKeyRange.only(src);
const request = index.get(range);
return new Promise((resolve, reject) => {
request.onerror = () => {
/**
* 读取文件失败
*/
this.onerror('Failed to read file');
reject('Failed to read file');
};
request.onsuccess = () => {
const file: OsFile = request.result;
if (file) {
this.dfsCopFile(file, objectStore, dest);
this.commitWatch(src, file.content);
}
resolve();
};
});
}
/**
* 设置指定文件或目录的权限
* @param path 文件或目录的路径
* @param mode 文件或目录的权限模式
* @returns 返回一个Promise,成功时无返回值,失败时抛出错误
*/
async chmod(path: string, mode: OsFileMode): Promise<void> {
const respath = fspath.resolve(path); // 解析路径
const volume = this.checkVolumePath(respath); // 检查路径是否属于某个卷
if (volume) {
return this.beforeGuard(volume, 'chmod', respath, mode); // 如果属于卷路径,则调用beforeGuard方法进行鉴权
}
const transaction = this.db.transaction('files', 'readwrite'); // 创建数据库事务
const objectStore = transaction.objectStore('files'); // 获取文件对象存储
const index = objectStore.index('path'); // 获取路径索引
const range = IDBKeyRange.only(respath); // 创建键值范围
const request = index.get(range); // 获取指定范围内的数据
return new Promise((resolve, reject) => {
request.onerror = () => {
this.onerror('Failed to read file'); // 请求错误时调用onerror方法
reject('Failed to read file'); // 抛出错误
};
request.onsuccess = () => {
const file: OsFile = request.result; // 获取请求结果
if (file) {
file.mode = mode; // 设置文件权限
file.mtime = new Date(); // 设置文件的修改时间
objectStore.put(file); // 更新数据库中的文件记录
} else {
reject('File not found'); // 文件不存在时抛出错误
}
resolve(); // 请求成功
};
});
}
/**
* 根据关键字搜索文件
* @param keyword 搜索关键字
* @returns 返回一个Promise,Promise的结果是一个OsFileWithoutContent类型的数组,表示搜索到的文件列表
*/
async search(keyword: string): Promise<OsFileWithoutContent[]> {
const transaction = this.db.transaction('files', 'readonly');
const objectStore = transaction.objectStore('files');
const index = objectStore.index('name');
const range = IDBKeyRange.bound(keyword, keyword + '\uffff');
const request = index.getAll(range);
return new Promise((resolve, reject) => {
request.onerror = () => {
this.onerror('搜索文件失败');
reject('搜索文件失败');
};
request.onsuccess = () => {
const files = request.result;
resolve(files);
};
});
}
async zip(path: string, ext?: string) {
ext = 'zip';
const ziplibs = new JSZip();
const that = this;
async function dfsPackage(path: string, ziplibs: JSZip) {
const dir = await that.readdir(path);
for (let i = 0; i < dir.length; i++) {
const item = dir[i];
const stat = await that.stat(item.path);
if (stat) {
if (stat.isDirectory) {
await dfsPackage(item.path, ziplibs);
} else {
const content = await that.readFile(item.path);
try {
atob(content || "");
ziplibs.file(item.path, content || "");
} catch (error) {
ziplibs.file(item.path, content || "");
}
}
}
}
}
await dfsPackage(path, ziplibs);
const content = await ziplibs.generateAsync({ type: "blob" })
const targetPath = path + "." + ext;
await that.writeFile(targetPath, content);
return { code: 0 }
}
async unzip(path: string) {
const stat = await this.stat(path);
if (stat?.isDirectory) {
return { code: -1 }
}
const that = this;
try {
const content = await this.readFile(path);
const ziplibs = new JSZip();
const unziped = await ziplibs.loadAsync(content);
const targetPath = path.substring(0, path.lastIndexOf("."))
if (!await that.exists(targetPath)) {
await that.mkdir(targetPath)
}
const unzipArray: Array<JSZip.JSZipObject> = [];
unziped.forEach((_, zipEntry) => {
unzipArray.push(zipEntry);
});
for (let i = 0; i < unzipArray.length; i++) {
const zipEntry = unzipArray[i];
if (zipEntry.dir) {
await this.mkdir(zipEntry.name);
} else {
let fileC: any = await zipEntry.async("string");
if (!fileC.startsWith("link::")) {
fileC = await zipEntry.async("arraybuffer");
}
this.writeFile(zipEntry.name, fileC);
}
}
return { code: 0 }
} catch (error) {
console.log(error)
return { code: -1 }
}
}
}
export { OsFile, OsFileInfo, OsFileSystem };