capacitor-advanced-file-manager
Version:
Advanced file manager plugin for Capacitor with comprehensive file system operations including browse, create, edit, delete, move, copy, and search files and directories.
470 lines (397 loc) • 14.7 kB
text/typescript
import { WebPlugin } from '@capacitor/core';
import type {
WebEnhancedFileManagerPlugin,
WebEnhancedOptions,
WebFileAccessResult,
VirtualFileSystem,
VirtualFile,
DragDropOptions
} from './definitions';
export class WebEnhancedFileManagerWeb extends WebPlugin implements WebEnhancedFileManagerPlugin {
private vfs: VirtualFileSystem | null = null;
private opfsRoot: FileSystemDirectoryHandle | null = null;
private indexedDB: IDBDatabase | null = null;
private workers: Map<string, Worker> = new Map();
async initializeWebFS(options: WebEnhancedOptions = {}): Promise<WebFileAccessResult> {
try {
// 初始化 OPFS
if (options.useOPFS && 'navigator' in globalThis && 'storage' in navigator) {
try {
this.opfsRoot = await navigator.storage.getDirectory();
console.log('✅ OPFS 初始化成功');
} catch (error) {
console.warn('⚠️ OPFS 初始化失败:', error);
}
}
// 初始化 IndexedDB
if (options.enableIndexedDBCache) {
await this.initIndexedDB();
}
// 创建虚拟文件系统
this.vfs = await this.createVirtualFS();
return {
success: true,
method: 'memory',
data: {
opfsAvailable: !!this.opfsRoot,
indexedDBAvailable: !!this.indexedDB,
vfsCreated: !!this.vfs
}
};
} catch (error) {
return {
success: false,
method: 'memory',
error: `初始化失败: ${error}`
};
}
}
async checkWebCapabilities() {
return {
fileSystemAccess: 'showOpenFilePicker' in window,
opfs: 'navigator' in globalThis && 'storage' in navigator && 'getDirectory' in navigator.storage,
indexedDB: 'indexedDB' in window,
webWorkers: 'Worker' in window,
streams: 'ReadableStream' in window,
dragDrop: 'DataTransfer' in window
};
}
async enableDragDropAccess(
element: HTMLElement,
options: DragDropOptions = {},
callback?: (files: File[], directories?: FileSystemDirectoryHandle[]) => void
): Promise<void> {
const handleDragOver = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
element.classList.add('drag-over');
};
const handleDragLeave = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
element.classList.remove('drag-over');
};
const handleDrop = async (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
element.classList.remove('drag-over');
const files: File[] = [];
const directories: FileSystemDirectoryHandle[] = [];
if (e.dataTransfer?.items) {
for (const item of Array.from(e.dataTransfer.items)) {
if (item.kind === 'file') {
const entry = item.webkitGetAsEntry?.();
if (entry?.isFile) {
const file = item.getAsFile();
if (file && this.validateFile(file, options)) {
files.push(file);
}
} else if (entry?.isDirectory && options.allowDirectories) {
// 处理目录拖拽(需要递归读取)
await this.processDirectoryEntry(entry as any, files, options);
}
}
}
}
if (callback) {
callback(files, directories);
}
// 自动添加到虚拟文件系统
if (this.vfs) {
for (const file of files) {
await this.addToVirtualFS(this.vfs, file, `/${file.name}`);
}
}
};
element.addEventListener('dragover', handleDragOver);
element.addEventListener('dragleave', handleDragLeave);
element.addEventListener('drop', handleDrop);
// 添加视觉反馈样式
const style = document.createElement('style');
style.textContent = `
.drag-over {
border: 2px dashed #007cba !important;
background-color: rgba(0, 124, 186, 0.1) !important;
}
`;
document.head.appendChild(style);
}
async createFileInput(options: {
multiple?: boolean;
accept?: string;
directory?: boolean;
} = {}): Promise<HTMLInputElement> {
const input = document.createElement('input');
input.type = 'file';
input.style.display = 'none';
if (options.multiple) input.multiple = true;
if (options.accept) input.accept = options.accept;
if (options.directory) {
input.webkitdirectory = true;
input.multiple = true;
}
// 自动处理文件选择
input.addEventListener('change', async (e) => {
const files = Array.from((e.target as HTMLInputElement).files || []);
if (this.vfs) {
for (const file of files) {
const path = options.directory ? file.webkitRelativePath : `/${file.name}`;
await this.addToVirtualFS(this.vfs, file, path);
}
}
});
document.body.appendChild(input);
return input;
}
async storeInOPFS(path: string, content: Blob | ArrayBuffer): Promise<WebFileAccessResult> {
if (!this.opfsRoot) {
return { success: false, method: 'opfs', error: 'OPFS 未初始化' };
}
try {
const pathParts = path.split('/').filter(p => p);
const fileName = pathParts.pop()!;
// 创建目录结构
let currentDir = this.opfsRoot;
for (const part of pathParts) {
currentDir = await currentDir.getDirectoryHandle(part, { create: true });
}
// 创建文件
const fileHandle = await currentDir.getFileHandle(fileName, { create: true });
const writable = await fileHandle.createWritable();
if (content instanceof ArrayBuffer) {
await writable.write(content);
} else {
await writable.write(content);
}
await writable.close();
return { success: true, method: 'opfs', data: { path, size: content.size || content.byteLength } };
} catch (error) {
return { success: false, method: 'opfs', error: `OPFS 存储失败: ${error}` };
}
}
async readFromOPFS(path: string): Promise<WebFileAccessResult> {
if (!this.opfsRoot) {
return { success: false, method: 'opfs', error: 'OPFS 未初始化' };
}
try {
const pathParts = path.split('/').filter(p => p);
const fileName = pathParts.pop()!;
let currentDir = this.opfsRoot;
for (const part of pathParts) {
currentDir = await currentDir.getDirectoryHandle(part);
}
const fileHandle = await currentDir.getFileHandle(fileName);
const file = await fileHandle.getFile();
return {
success: true,
method: 'opfs',
data: {
content: file,
size: file.size,
type: file.type,
lastModified: file.lastModified
}
};
} catch (error) {
return { success: false, method: 'opfs', error: `OPFS 读取失败: ${error}` };
}
}
async cacheInIndexedDB(path: string, content: Blob | ArrayBuffer, metadata: any = {}): Promise<WebFileAccessResult> {
if (!this.indexedDB) {
return { success: false, method: 'indexeddb', error: 'IndexedDB 未初始化' };
}
try {
const transaction = this.indexedDB.transaction(['files'], 'readwrite');
const store = transaction.objectStore('files');
const fileData = {
path,
content: content instanceof ArrayBuffer ? content : await content.arrayBuffer(),
metadata: {
...metadata,
size: content.size || content.byteLength,
timestamp: Date.now()
}
};
await new Promise((resolve, reject) => {
const request = store.put(fileData);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
return { success: true, method: 'indexeddb', data: { path, cached: true } };
} catch (error) {
return { success: false, method: 'indexeddb', error: `IndexedDB 缓存失败: ${error}` };
}
}
async readFromIndexedDB(path: string): Promise<WebFileAccessResult> {
if (!this.indexedDB) {
return { success: false, method: 'indexeddb', error: 'IndexedDB 未初始化' };
}
try {
const transaction = this.indexedDB.transaction(['files'], 'readonly');
const store = transaction.objectStore('files');
const fileData = await new Promise<any>((resolve, reject) => {
const request = store.get(path);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
if (!fileData) {
return { success: false, method: 'indexeddb', error: '文件未找到' };
}
return {
success: true,
method: 'indexeddb',
data: {
content: new Blob([fileData.content]),
metadata: fileData.metadata
}
};
} catch (error) {
return { success: false, method: 'indexeddb', error: `IndexedDB 读取失败: ${error}` };
}
}
async createVirtualFS(): Promise<VirtualFileSystem> {
return {
root: '/',
fileMap: new Map(),
directoryMap: new Map()
};
}
async addToVirtualFS(vfs: VirtualFileSystem, file: File | Blob, path: string): Promise<void> {
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
const pathParts = normalizedPath.split('/').filter(p => p);
const fileName = pathParts.pop() || 'unnamed';
// 创建目录结构
let currentPath = '';
for (const part of pathParts) {
currentPath += `/${part}`;
if (!vfs.directoryMap.has(currentPath)) {
vfs.directoryMap.set(currentPath, {
path: currentPath,
name: part,
files: [],
directories: [],
createdAt: Date.now(),
modifiedAt: Date.now()
});
}
}
// 添加文件
const virtualFile: VirtualFile = {
path: normalizedPath,
name: fileName,
content: file,
size: file.size,
mimeType: file.type || 'application/octet-stream',
createdAt: Date.now(),
modifiedAt: file instanceof File ? file.lastModified : Date.now()
};
vfs.fileMap.set(normalizedPath, virtualFile);
// 更新父目录
const parentPath = pathParts.length > 0 ? `/${pathParts.join('/')}` : '/';
const parentDir = vfs.directoryMap.get(parentPath);
if (parentDir && !parentDir.files.includes(fileName)) {
parentDir.files.push(fileName);
parentDir.modifiedAt = Date.now();
}
}
async readFromVirtualFS(vfs: VirtualFileSystem, path: string): Promise<VirtualFile | null> {
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
return vfs.fileMap.get(normalizedPath) || null;
}
private async initIndexedDB(): Promise<void> {
return new Promise((resolve, reject) => {
const request = indexedDB.open('AdvancedFileManager', 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.indexedDB = request.result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains('files')) {
db.createObjectStore('files', { keyPath: 'path' });
}
};
});
}
private validateFile(file: File, options: DragDropOptions): boolean {
if (options.maxFileSize && file.size > options.maxFileSize) {
return false;
}
if (options.allowedTypes && options.allowedTypes.length > 0) {
const extension = file.name.split('.').pop()?.toLowerCase();
return extension ? options.allowedTypes.includes(extension) : false;
}
return true;
}
private async processDirectoryEntry(entry: any, files: File[], options: DragDropOptions): Promise<void> {
// 递归处理目录项(WebKit API)
const reader = entry.createReader();
const entries = await new Promise<any[]>((resolve) => {
reader.readEntries(resolve);
});
for (const childEntry of entries) {
if (childEntry.isFile) {
const file = await new Promise<File>((resolve) => {
childEntry.file(resolve);
});
if (this.validateFile(file, options)) {
files.push(file);
}
} else if (childEntry.isDirectory) {
await this.processDirectoryEntry(childEntry, files, options);
}
}
}
// 其他方法的简化实现...
async processFileWithWorker(file: File | Blob, operation: string, options: any = {}): Promise<any> {
throw new Error('Web Worker 文件处理功能开发中');
}
async streamProcessFile(file: File, processor: (chunk: Uint8Array) => Promise<void>, chunkSize = 64 * 1024): Promise<void> {
const reader = file.stream().getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
await processor(value);
}
} finally {
reader.releaseLock();
}
}
async chunkedFileUpload(file: File, uploadUrl: string, chunkSize = 1024 * 1024, progressCallback?: (progress: number) => void): Promise<WebFileAccessResult> {
throw new Error('分片上传功能开发中');
}
async chunkedFileDownload(url: string, progressCallback?: (progress: number) => void): Promise<Blob> {
throw new Error('分片下载功能开发中');
}
async cacheWithServiceWorker(files: File[]): Promise<WebFileAccessResult> {
throw new Error('Service Worker 缓存功能开发中');
}
async proxyRequest(url: string, options?: RequestInit): Promise<Response> {
throw new Error('代理请求功能开发中');
}
async createDownloadLink(content: Blob | string, filename: string, options: { autoDownload?: boolean } = {}): Promise<string> {
const blob = content instanceof Blob ? content : new Blob([content]);
const url = URL.createObjectURL(blob);
if (options.autoDownload) {
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
return url;
}
async initP2PFileTransfer(): Promise<any> {
throw new Error('P2P 文件传输功能开发中');
}
async wasmFileProcess(file: File, wasmModule: string, operation: string): Promise<ArrayBuffer> {
throw new Error('WebAssembly 文件处理功能开发中');
}
async distributedStorage(file: File, storageProviders: string[]): Promise<any> {
throw new Error('分布式存储功能开发中');
}
}