import { WebPlugin } from '@capacitor/core'; import type { AdvancedFileManagerPlugin, FileInfo, ListDirectoryOptions, ListDirectoryResult, FileOperationOptions, CreateFileOptions, ReadFileOptions, ReadFileResult, WriteFileOptions, MoveFileOptions, CopyFileOptions, RenameFileOptions, CreateDirectoryOptions, SearchFilesOptions, SearchFilesResult, SearchContentOptions, SearchContentResult, PermissionResult, SystemFilePickerOptions, SystemFilePickerResult, SelectedFileInfo, // AI 编辑相关 ReadFileRangeOptions, ReadFileRangeResult, InsertContentOptions, ReplaceInFileOptions, ReplaceInFileResult, ApplyDiffOptions, ApplyDiffResult, GetFileHashOptions, GetFileHashResult, GetLineCountResult } from './definitions'; export class AdvancedFileManagerWeb extends WebPlugin implements AdvancedFileManagerPlugin { // 检查是否支持 File System Access API private isFileSystemAccessSupported(): boolean { return 'showDirectoryPicker' in window; } async requestPermissions(): Promise { if (this.isFileSystemAccessSupported()) { return { granted: true, message: 'File System Access API is supported' }; } else { return { granted: false, message: 'File System Access API is not supported in this browser' }; } } async checkPermissions(): Promise { return this.requestPermissions(); } async listDirectory(options: ListDirectoryOptions): Promise { if (!this.isFileSystemAccessSupported()) { throw new Error('File System Access API is not supported in this browser'); } try { // 在Web平台,我们需要用户选择目录 const dirHandle = await (window as any).showDirectoryPicker(); const files: FileInfo[] = []; for await (const [name, handle] of dirHandle.entries()) { const file = await this.getFileInfoFromHandle(handle, name, options.path); if (options.showHidden || !file.isHidden) { files.push(file); } } // 排序 if (options.sortBy) { files.sort((a, b) => { let comparison = 0; switch (options.sortBy) { case 'name': comparison = a.name.localeCompare(b.name); break; case 'size': comparison = a.size - b.size; break; case 'mtime': comparison = a.mtime - b.mtime; break; case 'type': comparison = a.type.localeCompare(b.type); break; } return options.sortOrder === 'desc' ? -comparison : comparison; }); } return { files, totalCount: files.length }; } catch (error) { throw new Error(`Failed to list directory: ${error}`); } } private async getFileInfoFromHandle(handle: any, name: string, basePath: string): Promise { const isDirectory = handle.kind === 'directory'; let size = 0; let mtime = 0; let ctime = 0; if (!isDirectory) { try { const file = await handle.getFile(); size = file.size; mtime = file.lastModified; ctime = file.lastModified; // Web API doesn't provide creation time } catch (error) { console.warn('Failed to get file info:', error); } } return { name, path: `${basePath}/${name}`, size, type: isDirectory ? 'directory' : 'file', mtime, ctime, isHidden: name.startsWith('.') }; } async createDirectory(_options: CreateDirectoryOptions): Promise { throw new Error('Creating directories is not supported in web browsers for security reasons'); } async deleteDirectory(_options: FileOperationOptions): Promise { throw new Error('Deleting directories is not supported in web browsers for security reasons'); } async createFile(options: CreateFileOptions): Promise { if (!this.isFileSystemAccessSupported()) { throw new Error('File System Access API is not supported in this browser'); } try { const fileHandle = await (window as any).showSaveFilePicker({ suggestedName: options.path.split('/').pop() || 'new-file.txt' }); const writable = await fileHandle.createWritable(); const content = options.content || ''; if (options.encoding === 'base64') { const binaryString = atob(content); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } await writable.write(bytes); } else { await writable.write(content); } await writable.close(); } catch (error) { throw new Error(`Failed to create file: ${error}`); } } async readFile(options: ReadFileOptions): Promise { if (!this.isFileSystemAccessSupported()) { throw new Error('File System Access API is not supported in this browser'); } try { const [fileHandle] = await (window as any).showOpenFilePicker(); const file = await fileHandle.getFile(); let content: string; const encoding = options.encoding || 'utf8'; if (encoding === 'base64') { const arrayBuffer = await file.arrayBuffer(); const bytes = new Uint8Array(arrayBuffer); content = btoa(String.fromCharCode(...bytes)); } else { content = await file.text(); } return { content, encoding }; } catch (error) { throw new Error(`Failed to read file: ${error}`); } } async writeFile(options: WriteFileOptions): Promise { // 在Web平台,写入文件通常需要用户交互 return this.createFile({ path: options.path, content: options.content, encoding: options.encoding }); } async deleteFile(_options: FileOperationOptions): Promise { throw new Error('Deleting files is not supported in web browsers for security reasons'); } async moveFile(_options: MoveFileOptions): Promise { throw new Error('Moving files is not supported in web browsers for security reasons'); } async copyFile(_options: CopyFileOptions): Promise { throw new Error('Copying files is not supported in web browsers for security reasons'); } async renameFile(_options: RenameFileOptions): Promise { throw new Error('Renaming files is not supported in web browsers for security reasons'); } async getFileInfo(_options: FileOperationOptions): Promise { throw new Error('Getting file info for specific paths is not supported in web browsers'); } async exists(_options: FileOperationOptions): Promise<{ exists: boolean }> { throw new Error('Checking file existence is not supported in web browsers for security reasons'); } async searchFiles(_options: SearchFilesOptions): Promise { throw new Error('File search is not supported in web browsers for security reasons'); } async searchContent(_options: SearchContentOptions): Promise { // Web 平台不支持直接文件系统访问,返回空结果 console.warn('searchContent is not fully supported in web browsers'); return { results: [], totalFiles: 0, totalMatches: 0, duration: 0, skippedFiles: 0 }; } async openSystemFilePicker(options: SystemFilePickerOptions): Promise { if (!this.isFileSystemAccessSupported()) { throw new Error('File System Access API is not supported in this browser'); } try { const files: SelectedFileInfo[] = []; const directories: SelectedFileInfo[] = []; if (options.type === 'directory' || options.type === 'both') { // 选择目录 const dirHandle = await (window as any).showDirectoryPicker(); const dirInfo: SelectedFileInfo = { name: dirHandle.name, path: `/${dirHandle.name}`, uri: dirHandle.name, size: 0, type: 'directory', mimeType: 'inode/directory', mtime: Date.now(), ctime: Date.now() }; directories.push(dirInfo); } if (options.type === 'file' || options.type === 'both') { // 选择文件 const fileHandles = await (window as any).showOpenFilePicker({ multiple: options.multiple || false, types: options.accept ? [{ description: 'Selected files', accept: options.accept.reduce((acc: any, ext: string) => { const mimeType = this.getMimeTypeFromExtension(ext); if (!acc[mimeType]) acc[mimeType] = []; acc[mimeType].push(`.${ext}`); return acc; }, {}) }] : undefined }); for (const handle of fileHandles) { try { const file = await handle.getFile(); const fileInfo: SelectedFileInfo = { name: file.name, path: `/${file.name}`, uri: file.name, size: file.size, type: 'file', mimeType: file.type || 'application/octet-stream', mtime: file.lastModified, ctime: file.lastModified }; files.push(fileInfo); } catch (error) { console.warn('Failed to get file info:', error); } } } return { files, directories, cancelled: false }; } catch (error: unknown) { if (error instanceof Error && error.name === 'AbortError') { return { files: [], directories: [], cancelled: true }; } throw new Error(`File picker failed: ${error}`); } } async openSystemFileManager(_path?: string): Promise { // Web浏览器无法直接打开系统文件管理器 // 但可以提供一些替代方案 if (this.isFileSystemAccessSupported()) { try { await (window as any).showDirectoryPicker(); } catch (error: unknown) { if (!(error instanceof Error) || error.name !== 'AbortError') { throw new Error('Cannot open system file manager in web browsers'); } } } else { throw new Error('File system access not supported in this browser'); } } async openFileWithSystemApp(_filePath: string, _mimeType?: string): Promise { throw new Error('Opening files with system apps is not supported in web browsers'); } // ============ AI 编辑操作 ============ async readFileRange(_options: ReadFileRangeOptions): Promise { throw new Error('Reading file ranges is not supported in web browsers for security reasons'); } async insertContent(_options: InsertContentOptions): Promise { throw new Error('Inserting content is not supported in web browsers for security reasons'); } async replaceInFile(_options: ReplaceInFileOptions): Promise { throw new Error('Replacing in file is not supported in web browsers for security reasons'); } async applyDiff(_options: ApplyDiffOptions): Promise { throw new Error('Applying diff is not supported in web browsers for security reasons'); } async getFileHash(_options: GetFileHashOptions): Promise { throw new Error('Getting file hash is not supported in web browsers for security reasons'); } async getLineCount(_options: FileOperationOptions): Promise { throw new Error('Getting line count is not supported in web browsers for security reasons'); } private getMimeTypeFromExtension(extension: string): string { const mimeTypes: { [key: string]: string } = { 'txt': 'text/plain', 'pdf': 'application/pdf', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png', 'gif': 'image/gif', 'mp4': 'video/mp4', 'mp3': 'audio/mpeg', 'zip': 'application/zip', 'json': 'application/json', 'xml': 'application/xml', 'html': 'text/html', 'css': 'text/css', 'js': 'application/javascript' }; return mimeTypes[extension.toLowerCase()] || 'application/octet-stream'; } async echo(options: { value: string }): Promise<{ value: string }> { console.log('ECHO', options); return options; } }