@huyooo/file-explorer-core 0.3.0 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1380 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +427 -12
- package/dist/index.d.ts +427 -12
- package/dist/index.js +1381 -127
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/protocol.ts","../src/utils/file-type.ts","../src/utils/formatters.ts","../src/operations/read.ts","../src/operations/write.ts","../src/operations/delete.ts","../src/operations/rename.ts","../src/operations/copy.ts","../src/operations/info.ts","../src/system-paths.ts","../src/search.ts","../src/hash.ts","../src/clipboard.ts","../src/application-icon.ts","../src/thumbnail/service.ts","../src/thumbnail/database.ts","../src/thumbnail/processors.ts"],"sourcesContent":["/**\n * 文件类型枚举\n */\nexport enum FileType {\n FOLDER = 'folder',\n FILE = 'file',\n IMAGE = 'image',\n VIDEO = 'video',\n MUSIC = 'music',\n DOCUMENT = 'document',\n CODE = 'code',\n TEXT = 'text',\n ARCHIVE = 'archive',\n APPLICATION = 'application',\n UNKNOWN = 'unknown'\n}\n\n/**\n * 文件系统项\n */\nexport interface FileItem {\n /** 唯一标识(通常是完整路径) */\n id: string;\n /** 文件名 */\n name: string;\n /** 文件类型 */\n type: FileType;\n /** 文件大小(格式化后的字符串) */\n size?: string;\n /** 修改日期(格式化后的字符串) */\n dateModified?: string;\n /** 文件 URL(用于加载) */\n url?: string;\n /** 缩略图 URL */\n thumbnailUrl?: string;\n /** 子项(仅文件夹) */\n children?: FileItem[];\n}\n\n/**\n * 文件信息\n */\nexport interface FileInfo {\n path: string;\n name: string;\n size: number;\n isFile: boolean;\n isDirectory: boolean;\n createdAt: Date;\n updatedAt: Date;\n extension?: string;\n}\n\n/**\n * 操作结果\n */\nexport interface OperationResult<T = void> {\n success: boolean;\n data?: T;\n error?: string;\n message?: string;\n}\n\n/**\n * 系统路径 ID\n */\nexport type SystemPathId = \n | 'desktop' \n | 'documents' \n | 'downloads' \n | 'pictures' \n | 'music'\n | 'videos'\n | 'applications' \n | 'home' \n | 'root';\n\n/**\n * 平台适配器接口\n * 用于处理平台特定的操作(如 Electron 的 shell.trashItem)\n */\nexport interface PlatformAdapter {\n /** 删除文件到回收站 */\n trashItem?: (path: string) => Promise<void>;\n /** 打开文件 */\n openPath?: (path: string) => Promise<void>;\n /** 使用指定应用打开 */\n openWith?: (path: string, appPath: string) => Promise<void>;\n}\n\n/**\n * 文件操作选项\n */\nexport interface FileOperationOptions {\n /** 平台适配器 */\n adapter?: PlatformAdapter;\n /** 是否自动重命名(当目标存在时) */\n autoRename?: boolean;\n}\n","/**\n * 自定义协议工具模块\n * \n * 统一处理 app:// 协议的编码和解码\n * \n * 协议格式: app://file/path/to/file.jpg\n * - scheme: app\n * - host: file\n * - path: /path/to/file.jpg(路径段编码,保留 /)\n */\n\n/** 协议前缀 */\nexport const APP_PROTOCOL_SCHEME = 'app';\nexport const APP_PROTOCOL_HOST = 'file';\nexport const APP_PROTOCOL_PREFIX = `${APP_PROTOCOL_SCHEME}://${APP_PROTOCOL_HOST}`;\n\n/**\n * 编码文件路径为 app:// 协议 URL\n * 分段编码路径,保留 / 分隔符,符合 URL 标准\n * \n * @param filePath - 文件的绝对路径,如 /Users/name/file.jpg\n * @returns app:// 协议 URL,如 app://file/Users/name/file.jpg\n * \n * @example\n * encodeFileUrl('/Users/name/my file.jpg')\n * // => 'app://file/Users/name/my%20file.jpg'\n */\nexport function encodeFileUrl(filePath: string): string {\n if (!filePath) return '';\n \n // 分割路径,编码每个段,再用 / 连接\n const encodedPath = filePath\n .split('/')\n .map(segment => encodeURIComponent(segment))\n .join('/');\n \n return `${APP_PROTOCOL_PREFIX}${encodedPath}`;\n}\n\n/**\n * 解码 app:// 协议 URL 为文件路径\n * \n * @param url - app:// 协议 URL\n * @returns 文件的绝对路径\n * \n * @example\n * decodeFileUrl('app://file/Users/name/my%20file.jpg')\n * // => '/Users/name/my file.jpg'\n */\nexport function decodeFileUrl(url: string): string {\n if (!url) return '';\n \n // 移除协议前缀\n let path = url;\n if (path.startsWith(APP_PROTOCOL_PREFIX)) {\n path = path.slice(APP_PROTOCOL_PREFIX.length);\n }\n \n // 移除 hash 部分(如视频时间片段 #t=0,104.03)\n const hashIndex = path.indexOf('#');\n if (hashIndex !== -1) {\n path = path.substring(0, hashIndex);\n }\n \n // 移除查询参数\n const queryIndex = path.indexOf('?');\n if (queryIndex !== -1) {\n path = path.substring(0, queryIndex);\n }\n \n // 解码路径\n try {\n return decodeURIComponent(path);\n } catch {\n return path;\n }\n}\n\n/**\n * 检查 URL 是否为 app:// 协议\n */\nexport function isAppProtocolUrl(url: string): boolean {\n return url?.startsWith(APP_PROTOCOL_PREFIX) ?? false;\n}\n","import path from 'node:path';\nimport type { Stats } from 'node:fs';\nimport { FileType } from '../types';\n\n/** 图片扩展名 */\nconst IMAGE_EXTENSIONS = new Set([\n '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.ico', '.tiff', '.tif', '.heic', '.heif'\n]);\n\n/** 视频扩展名 */\nconst VIDEO_EXTENSIONS = new Set([\n '.mp4', '.mov', '.avi', '.mkv', '.wmv', '.flv', '.webm', '.m4v', '.3gp', '.mpeg', '.mpg'\n]);\n\n/** 音频扩展名 */\nconst MUSIC_EXTENSIONS = new Set([\n '.mp3', '.wav', '.flac', '.aac', '.ogg', '.wma', '.m4a', '.aiff', '.alac'\n]);\n\n/** 文档扩展名 */\nconst DOCUMENT_EXTENSIONS = new Set([\n '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.odt', '.ods', '.odp', '.rtf', '.pages', '.numbers', '.key'\n]);\n\n/** 代码扩展名 */\nconst CODE_EXTENSIONS = new Set([\n '.js', '.ts', '.jsx', '.tsx', '.vue', '.svelte',\n '.py', '.rb', '.go', '.rs', '.java', '.kt', '.swift', '.c', '.cpp', '.h', '.hpp',\n '.html', '.css', '.scss', '.sass', '.less',\n '.json', '.yaml', '.yml', '.toml', '.xml',\n '.sh', '.bash', '.zsh', '.fish', '.ps1',\n '.sql', '.graphql', '.prisma'\n]);\n\n/** 文本扩展名 */\nconst TEXT_EXTENSIONS = new Set([\n '.txt', '.md', '.markdown', '.log', '.ini', '.conf', '.cfg', '.env'\n]);\n\n/** 压缩包扩展名 */\nconst ARCHIVE_EXTENSIONS = new Set([\n '.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.xz', '.dmg', '.iso'\n]);\n\n/** 应用程序扩展名 */\nconst APPLICATION_EXTENSIONS = new Set([\n '.app', '.exe', '.msi', '.deb', '.rpm', '.pkg', '.apk', '.ipa'\n]);\n\n/**\n * 根据文件路径和状态获取文件类型\n */\nexport function getFileType(filePath: string, stats: Stats): FileType {\n if (stats.isDirectory()) {\n // 检查是否是 macOS 应用程序包\n if (filePath.endsWith('.app')) {\n return FileType.APPLICATION;\n }\n return FileType.FOLDER;\n }\n\n const ext = path.extname(filePath).toLowerCase();\n\n if (IMAGE_EXTENSIONS.has(ext)) return FileType.IMAGE;\n if (VIDEO_EXTENSIONS.has(ext)) return FileType.VIDEO;\n if (MUSIC_EXTENSIONS.has(ext)) return FileType.MUSIC;\n if (DOCUMENT_EXTENSIONS.has(ext)) return FileType.DOCUMENT;\n if (CODE_EXTENSIONS.has(ext)) return FileType.CODE;\n if (TEXT_EXTENSIONS.has(ext)) return FileType.TEXT;\n if (ARCHIVE_EXTENSIONS.has(ext)) return FileType.ARCHIVE;\n if (APPLICATION_EXTENSIONS.has(ext)) return FileType.APPLICATION;\n\n return FileType.FILE;\n}\n\n/**\n * 判断是否为媒体文件\n */\nexport function isMediaFile(type: FileType): boolean {\n return type === FileType.IMAGE || type === FileType.VIDEO || type === FileType.MUSIC;\n}\n\n/**\n * 判断是否为可预览的文件\n */\nexport function isPreviewable(type: FileType): boolean {\n return type === FileType.IMAGE || type === FileType.VIDEO || type === FileType.MUSIC || \n type === FileType.TEXT || type === FileType.CODE;\n}\n","/**\n * 格式化文件大小\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return '0 B';\n \n const units = ['B', 'KB', 'MB', 'GB', 'TB'];\n const k = 1024;\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n \n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${units[i]}`;\n}\n\n/**\n * 格式化日期\n */\nexport function formatDate(date: Date): string {\n const now = new Date();\n const diff = now.getTime() - date.getTime();\n const days = Math.floor(diff / (1000 * 60 * 60 * 24));\n \n if (days === 0) {\n return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });\n } else if (days === 1) {\n return '昨天';\n } else if (days < 7) {\n return `${days} 天前`;\n } else {\n return date.toLocaleDateString('zh-CN', { \n year: 'numeric', \n month: '2-digit', \n day: '2-digit' \n });\n }\n}\n\n/**\n * 格式化日期时间\n */\nexport function formatDateTime(date: Date): string {\n return date.toLocaleString('zh-CN', {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit'\n });\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { FileItem } from '../types';\nimport { FileType } from '../types';\nimport { getFileType } from '../utils/file-type';\nimport { formatFileSize, formatDate } from '../utils/formatters';\n\n/**\n * URL 编码器(可由外部注入)\n */\nexport type UrlEncoder = (filePath: string) => string;\n\n/** 默认 URL 编码器 */\nconst defaultUrlEncoder: UrlEncoder = (filePath) => `file://${encodeURIComponent(filePath)}`;\n\n/**\n * 读取目录配置\n */\nexport interface ReadDirectoryOptions {\n /** URL 编码器 */\n urlEncoder?: UrlEncoder;\n /** 是否包含隐藏文件 */\n includeHidden?: boolean;\n /** 缩略图 URL 获取器(同步获取缓存的缩略图,没有则返回 null 并异步生成) */\n getThumbnailUrl?: (filePath: string) => Promise<string | null>;\n}\n\n/**\n * 读取目录内容\n */\nexport async function readDirectory(\n dirPath: string, \n options: ReadDirectoryOptions = {}\n): Promise<FileItem[]> {\n const { \n urlEncoder = defaultUrlEncoder,\n includeHidden = false,\n getThumbnailUrl\n } = options;\n\n try {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n const items: FileItem[] = [];\n\n for (const entry of entries) {\n // 跳过隐藏文件(以 . 开头)\n if (!includeHidden && entry.name.startsWith('.')) {\n continue;\n }\n\n const fullPath = path.join(dirPath, entry.name);\n\n try {\n // 使用 lstat 避免跟随符号链接\n let stats;\n try {\n stats = await fs.lstat(fullPath);\n } catch {\n try {\n stats = await fs.stat(fullPath);\n } catch (statError: unknown) {\n const err = statError as NodeJS.ErrnoException;\n if (err.code !== 'ENOENT') {\n console.warn(`Cannot access file ${fullPath}:`, err.message);\n }\n continue;\n }\n }\n\n const fileType = getFileType(fullPath, stats);\n const fileUrl = urlEncoder(fullPath);\n\n const item: FileItem = {\n id: fullPath,\n name: entry.name,\n type: fileType,\n dateModified: formatDate(stats.mtime),\n url: fileUrl,\n };\n\n if (stats.isDirectory()) {\n item.children = []; // 延迟加载\n } else {\n item.size = formatFileSize(stats.size);\n\n // 同步获取缩略图 URL(如果已缓存)\n if (getThumbnailUrl && (fileType === FileType.IMAGE || fileType === FileType.VIDEO)) {\n const thumbUrl = await getThumbnailUrl(fullPath);\n if (thumbUrl) {\n item.thumbnailUrl = thumbUrl;\n }\n }\n }\n\n items.push(item);\n } catch (itemError: unknown) {\n const err = itemError as NodeJS.ErrnoException;\n if (err.code !== 'ENOENT') {\n console.warn(`Error processing file ${fullPath}:`, err.message);\n }\n continue;\n }\n }\n\n // 排序:文件夹优先,然后按名称\n items.sort((a, b) => {\n if (a.type === FileType.FOLDER && b.type !== FileType.FOLDER) return -1;\n if (a.type !== FileType.FOLDER && b.type === FileType.FOLDER) return 1;\n return a.name.localeCompare(b.name, 'zh-CN');\n });\n\n return items;\n } catch (error) {\n console.error(`Error reading directory ${dirPath}:`, error);\n return [];\n }\n}\n\n/**\n * 读取文件内容\n */\nexport async function readFileContent(filePath: string): Promise<string> {\n return await fs.readFile(filePath, 'utf-8');\n}\n\n/**\n * 读取图片为 Base64\n */\nexport async function readImageAsBase64(imagePath: string): Promise<{\n success: boolean;\n base64?: string;\n mimeType?: string;\n error?: string;\n}> {\n try {\n // 处理各种 URL 格式,解码为实际文件路径\n let actualPath = imagePath;\n if (imagePath.startsWith('app://file')) {\n // 移除 app://file 前缀,然后解码\n const urlPath = imagePath.slice('app://file'.length);\n actualPath = decodeURIComponent(urlPath);\n } else if (imagePath.startsWith('file://')) {\n actualPath = decodeURIComponent(imagePath.replace('file://', ''));\n }\n\n const stats = await fs.stat(actualPath);\n if (!stats.isFile()) {\n return { success: false, error: `路径不是文件: ${actualPath}` };\n }\n\n const buffer = await fs.readFile(actualPath);\n const base64 = buffer.toString('base64');\n const ext = path.extname(actualPath).toLowerCase().slice(1);\n \n const mimeTypes: Record<string, string> = {\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n bmp: 'image/bmp',\n svg: 'image/svg+xml',\n };\n \n const mimeType = mimeTypes[ext] || 'image/jpeg';\n return { success: true, base64, mimeType };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { OperationResult } from '../types';\n\n/**\n * 写入文件内容\n */\nexport async function writeFileContent(\n filePath: string, \n content: string\n): Promise<OperationResult> {\n try {\n await fs.writeFile(filePath, content, 'utf-8');\n return { success: true };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 创建文件夹(自动处理同名文件夹)\n */\nexport async function createFolder(\n folderPath: string\n): Promise<OperationResult<{ finalPath: string }>> {\n try {\n // 检查是否存在\n try {\n await fs.access(folderPath);\n // 存在,需要自动重命名\n } catch {\n // 不存在,直接创建\n await fs.mkdir(folderPath, { recursive: true });\n return { success: true, data: { finalPath: folderPath } };\n }\n\n // 自动重命名\n const dir = path.dirname(folderPath);\n const baseName = path.basename(folderPath);\n let counter = 2;\n let finalPath = path.join(dir, `${baseName} ${counter}`);\n\n while (true) {\n try {\n await fs.access(finalPath);\n counter++;\n finalPath = path.join(dir, `${baseName} ${counter}`);\n } catch {\n break;\n }\n }\n\n await fs.mkdir(finalPath, { recursive: true });\n return { success: true, data: { finalPath } };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 创建文件(自动处理同名文件)\n */\nexport async function createFile(\n filePath: string,\n content: string = ''\n): Promise<OperationResult<{ finalPath: string }>> {\n try {\n // 确保父目录存在\n const dir = path.dirname(filePath);\n await fs.mkdir(dir, { recursive: true });\n\n // 检查是否存在\n try {\n await fs.access(filePath);\n // 存在,需要自动重命名\n } catch {\n // 不存在,直接创建\n await fs.writeFile(filePath, content, 'utf-8');\n return { success: true, data: { finalPath: filePath } };\n }\n\n // 自动重命名(保留扩展名)\n const dirname = path.dirname(filePath);\n const ext = path.extname(filePath);\n const basename = path.basename(filePath, ext);\n let counter = 2;\n let finalPath = path.join(dirname, `${basename} ${counter}${ext}`);\n\n while (true) {\n try {\n await fs.access(finalPath);\n counter++;\n finalPath = path.join(dirname, `${basename} ${counter}${ext}`);\n } catch {\n break;\n }\n }\n\n await fs.writeFile(finalPath, content, 'utf-8');\n return { success: true, data: { finalPath } };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n","import { promises as fs } from 'node:fs';\nimport type { OperationResult, PlatformAdapter } from '../types';\n\n/**\n * 删除文件选项\n */\nexport interface DeleteOptions {\n /** 平台适配器(用于移到回收站) */\n adapter?: PlatformAdapter;\n /** 是否使用回收站(默认 true) */\n useTrash?: boolean;\n /** 删除后回调(用于清理缩略图等) */\n onDeleted?: (path: string) => void;\n}\n\n/**\n * 删除文件/文件夹\n * \n * 如果提供了 adapter.trashItem,则移到回收站\n * 否则直接删除(危险操作)\n */\nexport async function deleteFiles(\n paths: string[],\n options: DeleteOptions = {}\n): Promise<OperationResult> {\n const { adapter, useTrash = true, onDeleted } = options;\n\n try {\n for (const filePath of paths) {\n if (useTrash && adapter?.trashItem) {\n // 使用平台特定的回收站功能\n await adapter.trashItem(filePath);\n } else {\n // 直接删除(谨慎使用)\n const stats = await fs.stat(filePath);\n if (stats.isDirectory()) {\n await fs.rm(filePath, { recursive: true, force: true });\n } else {\n await fs.unlink(filePath);\n }\n }\n\n // 回调\n onDeleted?.(filePath);\n }\n\n return { \n success: true, \n message: useTrash ? '文件已移动到回收站' : '文件已删除' \n };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n","import { promises as fs } from 'node:fs';\nimport type { OperationResult } from '../types';\n\n/**\n * 重命名选项\n */\nexport interface RenameOptions {\n /** 重命名后回调 */\n onRenamed?: (oldPath: string, newPath: string) => void;\n}\n\n/**\n * 重命名文件/文件夹\n */\nexport async function renameFile(\n oldPath: string,\n newPath: string,\n options: RenameOptions = {}\n): Promise<OperationResult> {\n const { onRenamed } = options;\n\n try {\n await fs.rename(oldPath, newPath);\n onRenamed?.(oldPath, newPath);\n return { success: true };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { OperationResult } from '../types';\n\n/**\n * 复制文件到目标目录\n */\nexport async function copyFiles(\n sourcePaths: string[],\n targetDir: string\n): Promise<OperationResult<{ copiedPaths: string[] }>> {\n try {\n const copiedPaths: string[] = [];\n\n for (const sourcePath of sourcePaths) {\n const fileName = path.basename(sourcePath);\n let destPath = path.join(targetDir, fileName);\n let counter = 1;\n\n // 自动重命名\n while (true) {\n try {\n await fs.access(destPath);\n const ext = path.extname(fileName);\n const baseName = path.basename(fileName, ext);\n destPath = path.join(targetDir, `${baseName} ${++counter}${ext}`);\n } catch {\n break;\n }\n }\n\n // 复制\n const stats = await fs.stat(sourcePath);\n if (stats.isDirectory()) {\n await copyDirectory(sourcePath, destPath);\n } else {\n await fs.copyFile(sourcePath, destPath);\n }\n\n copiedPaths.push(destPath);\n }\n\n return { success: true, data: { copiedPaths } };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 移动文件到目标目录\n */\nexport async function moveFiles(\n sourcePaths: string[],\n targetDir: string\n): Promise<OperationResult<{ movedPaths: string[] }>> {\n try {\n const movedPaths: string[] = [];\n\n for (const sourcePath of sourcePaths) {\n const fileName = path.basename(sourcePath);\n let destPath = path.join(targetDir, fileName);\n let counter = 1;\n\n // 自动重命名\n while (true) {\n try {\n await fs.access(destPath);\n const ext = path.extname(fileName);\n const baseName = path.basename(fileName, ext);\n destPath = path.join(targetDir, `${baseName} ${++counter}${ext}`);\n } catch {\n break;\n }\n }\n\n // 移动(使用 rename,如果跨磁盘则先复制后删除)\n try {\n await fs.rename(sourcePath, destPath);\n } catch {\n const stats = await fs.stat(sourcePath);\n if (stats.isDirectory()) {\n await copyDirectory(sourcePath, destPath);\n } else {\n await fs.copyFile(sourcePath, destPath);\n }\n await fs.rm(sourcePath, { recursive: true, force: true });\n }\n\n movedPaths.push(destPath);\n }\n\n return { success: true, data: { movedPaths } };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 递归复制目录\n */\nasync function copyDirectory(source: string, dest: string): Promise<void> {\n await fs.mkdir(dest, { recursive: true });\n const entries = await fs.readdir(source, { withFileTypes: true });\n\n for (const entry of entries) {\n const sourcePath = path.join(source, entry.name);\n const destPath = path.join(dest, entry.name);\n\n if (entry.isDirectory()) {\n await copyDirectory(sourcePath, destPath);\n } else {\n await fs.copyFile(sourcePath, destPath);\n }\n }\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { FileInfo, OperationResult } from '../types';\n\n/**\n * 获取文件信息\n */\nexport async function getFileInfo(\n filePath: string\n): Promise<OperationResult<FileInfo>> {\n try {\n const stats = await fs.stat(filePath);\n const name = path.basename(filePath);\n const ext = path.extname(name);\n\n return {\n success: true,\n data: {\n path: filePath,\n name,\n size: stats.size,\n isFile: stats.isFile(),\n isDirectory: stats.isDirectory(),\n createdAt: stats.birthtime,\n updatedAt: stats.mtime,\n extension: ext || undefined,\n },\n };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 检查文件/目录是否存在\n */\nexport async function exists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * 检查是否为目录\n */\nexport async function isDirectory(filePath: string): Promise<boolean> {\n try {\n const stats = await fs.stat(filePath);\n return stats.isDirectory();\n } catch {\n return false;\n }\n}\n","import path from 'node:path';\nimport os from 'node:os';\nimport type { SystemPathId } from './types';\n\nconst platform = process.platform;\n\n/**\n * 获取系统路径\n */\nexport function getSystemPath(pathId: SystemPathId): string | null {\n const homeDir = os.homedir();\n\n switch (pathId) {\n case 'desktop':\n return path.join(homeDir, 'Desktop');\n case 'documents':\n return path.join(homeDir, 'Documents');\n case 'downloads':\n return path.join(homeDir, 'Downloads');\n case 'pictures':\n return path.join(homeDir, 'Pictures');\n case 'music':\n return path.join(homeDir, 'Music');\n case 'videos':\n return platform === 'darwin'\n ? path.join(homeDir, 'Movies')\n : path.join(homeDir, 'Videos');\n case 'applications':\n return platform === 'darwin'\n ? '/Applications'\n : platform === 'win32'\n ? process.env.ProgramFiles || 'C:\\\\Program Files'\n : '/usr/share/applications';\n case 'home':\n return homeDir;\n case 'root':\n return platform === 'darwin' ? '/' : platform === 'win32' ? 'C:\\\\' : '/';\n default:\n return null;\n }\n}\n\n/**\n * 获取所有系统路径\n */\nexport function getAllSystemPaths(): Record<SystemPathId, string | null> {\n const pathIds: SystemPathId[] = [\n 'desktop', 'documents', 'downloads', 'pictures', \n 'music', 'videos', 'applications', 'home', 'root'\n ];\n\n const result = {} as Record<SystemPathId, string | null>;\n for (const id of pathIds) {\n result[id] = getSystemPath(id);\n }\n return result;\n}\n\n/**\n * 获取用户主目录\n */\nexport function getHomeDirectory(): string {\n return os.homedir();\n}\n\n/**\n * 获取当前平台\n */\nexport function getPlatform(): NodeJS.Platform {\n return process.platform;\n}\n","import { fdir } from 'fdir';\nimport path from 'node:path';\nimport { promises as fs } from 'node:fs';\n\n/**\n * 将通配符模式转为正则表达式\n */\nfunction patternToRegex(pattern: string): RegExp {\n return new RegExp(pattern.replace(/\\*/g, '.*'), 'i');\n}\n\n/**\n * 使用 fdir 快速搜索文件和文件夹\n * fdir 可以在 1 秒内扫描 100 万个文件\n * \n * @param searchPath 搜索路径\n * @param pattern 搜索模式(支持 * 通配符,忽略大小写)\n * @param maxDepth 最大递归深度\n */\nexport async function searchFiles(\n searchPath: string,\n pattern?: string,\n maxDepth?: number\n): Promise<string[]> {\n const builder = new fdir()\n .withFullPaths()\n .withDirs(); // 包含文件夹\n \n // maxDepth 为 0 或 undefined 时不限制深度\n if (maxDepth && maxDepth > 0) {\n builder.withMaxDepth(maxDepth);\n }\n \n const api = builder.crawl(searchPath);\n\n const files = await api.withPromise();\n\n if (pattern) {\n // 通配符转正则,忽略大小写\n const regex = patternToRegex(pattern);\n return (files as string[]).filter((file: string) => regex.test(path.basename(file)));\n }\n\n return files as string[];\n}\n\n/**\n * 流式搜索文件(逐层搜索,边搜边返回)\n * \n * @param searchPath 搜索路径\n * @param pattern 搜索模式\n * @param onResults 找到结果时的回调\n * @param maxResults 最大结果数\n */\nexport async function searchFilesStream(\n searchPath: string,\n pattern: string,\n onResults: (paths: string[], done: boolean) => void,\n maxResults: number = 100\n): Promise<void> {\n const regex = patternToRegex(pattern);\n const results: string[] = [];\n let stopped = false;\n \n // 递归搜索目录\n async function searchDir(dirPath: string): Promise<void> {\n if (stopped) return;\n \n try {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n const matched: string[] = [];\n const subdirs: string[] = [];\n \n for (const entry of entries) {\n if (stopped) return;\n \n const fullPath = path.join(dirPath, entry.name);\n \n // 检查是否匹配\n if (regex.test(entry.name)) {\n matched.push(fullPath);\n results.push(fullPath);\n \n // 达到最大数量时停止\n if (results.length >= maxResults) {\n stopped = true;\n onResults(matched, true);\n return;\n }\n }\n \n // 收集子目录\n if (entry.isDirectory()) {\n subdirs.push(fullPath);\n }\n }\n \n // 如果有匹配,立即推送\n if (matched.length > 0) {\n onResults(matched, false);\n }\n \n // 递归搜索子目录\n for (const subdir of subdirs) {\n await searchDir(subdir);\n }\n } catch {\n // 忽略无权限的目录\n }\n }\n \n await searchDir(searchPath);\n \n // 搜索完成\n if (!stopped) {\n onResults([], true);\n }\n}\n\n/**\n * 同步搜索(用于小目录)\n */\nexport function searchFilesSync(\n searchPath: string,\n pattern?: string,\n maxDepth?: number\n): string[] {\n const builder = new fdir()\n .withFullPaths()\n .withDirs(); // 包含文件夹\n \n // maxDepth 为 0 或 undefined 时不限制深度\n if (maxDepth && maxDepth > 0) {\n builder.withMaxDepth(maxDepth);\n }\n \n const api = builder.crawl(searchPath);\n const files = api.sync();\n\n if (pattern) {\n const regex = new RegExp(pattern.replace(/\\*/g, '.*'), 'i');\n return (files as string[]).filter((file: string) => regex.test(path.basename(file)));\n }\n\n return files as string[];\n}\n","import { createHash } from 'node:crypto';\nimport { stat } from 'node:fs/promises';\n\n/**\n * 计算文件的快速 hash(使用文件大小和修改时间)\n * 这比计算完整文件 hash 快得多,适合用于缓存判断\n */\nexport async function getFileHash(filePath: string): Promise<string> {\n try {\n const stats = await stat(filePath);\n // 使用文件大小、修改时间和路径的组合\n const hashInput = `${filePath}:${stats.size}:${stats.mtime.getTime()}`;\n return createHash('md5').update(hashInput).digest('hex');\n } catch (error) {\n console.error(`Error computing hash for ${filePath}:`, error);\n // 如果出错,返回基于路径的简单 hash\n return createHash('md5').update(filePath).digest('hex');\n }\n}\n\n/**\n * 批量计算文件 hash\n */\nexport async function getFileHashes(filePaths: string[]): Promise<Map<string, string>> {\n const hashMap = new Map<string, string>();\n \n await Promise.allSettled(\n filePaths.map(async (filePath) => {\n const hash = await getFileHash(filePath);\n hashMap.set(filePath, hash);\n })\n );\n \n return hashMap;\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { OperationResult } from './types';\n\n/**\n * 剪贴板文件操作接口\n * 由于 clipboard-files 是 native 模块,由使用者在 Electron 主进程中注入\n */\nexport interface ClipboardAdapter {\n writeFiles: (paths: string[]) => void;\n readFiles: () => string[];\n}\n\n/**\n * 复制文件到剪贴板\n */\nexport async function copyFilesToClipboard(\n filePaths: string[],\n clipboard: ClipboardAdapter\n): Promise<OperationResult> {\n try {\n // 验证文件是否存在\n const cleanPaths: string[] = [];\n for (const p of filePaths) {\n try {\n await fs.access(p);\n cleanPaths.push(p);\n } catch {\n // 文件不存在,跳过\n }\n }\n\n if (cleanPaths.length === 0) {\n return { success: false, error: '没有找到有效的文件' };\n }\n\n clipboard.writeFiles(cleanPaths);\n return { success: true };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 从剪贴板读取文件路径\n */\nexport function getClipboardFiles(\n clipboard: ClipboardAdapter\n): OperationResult<{ files: string[] }> {\n try {\n const files = clipboard.readFiles();\n if (files && Array.isArray(files) && files.length > 0) {\n return { success: true, data: { files } };\n }\n return { success: false, error: '剪贴板中没有文件' };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 粘贴文件到目标目录\n */\nexport async function pasteFiles(\n targetDir: string,\n sourcePaths: string[]\n): Promise<OperationResult<{ pastedPaths: string[] }>> {\n try {\n if (!targetDir || typeof targetDir !== 'string') {\n return { success: false, error: '目标目录路径无效' };\n }\n\n if (!sourcePaths || !Array.isArray(sourcePaths) || sourcePaths.length === 0) {\n return { success: false, error: '源文件路径列表无效' };\n }\n\n const pastedPaths: string[] = [];\n\n for (const sourcePath of sourcePaths) {\n const fileName = path.basename(sourcePath);\n let destPath = path.join(targetDir, fileName);\n let counter = 1;\n\n // 自动重命名\n while (true) {\n try {\n await fs.access(destPath);\n const ext = path.extname(fileName);\n const baseName = path.basename(fileName, ext);\n destPath = path.join(targetDir, `${baseName} ${++counter}${ext}`);\n } catch {\n break;\n }\n }\n\n // 复制\n const stats = await fs.stat(sourcePath);\n if (stats.isDirectory()) {\n await copyDirectory(sourcePath, destPath);\n } else {\n await fs.copyFile(sourcePath, destPath);\n }\n\n pastedPaths.push(destPath);\n }\n\n return { success: true, data: { pastedPaths } };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 递归复制目录\n */\nasync function copyDirectory(source: string, dest: string): Promise<void> {\n await fs.mkdir(dest, { recursive: true });\n const entries = await fs.readdir(source, { withFileTypes: true });\n\n for (const entry of entries) {\n const sourcePath = path.join(source, entry.name);\n const destPath = path.join(dest, entry.name);\n\n if (entry.isDirectory()) {\n await copyDirectory(sourcePath, destPath);\n } else {\n await fs.copyFile(sourcePath, destPath);\n }\n }\n}\n","/**\n * 应用程序图标获取(macOS)\n * \n * 纯 Node.js 实现,使用 sips 命令转换 icns 为 PNG\n */\n\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execAsync = promisify(exec);\n\n/**\n * 从应用程序包中查找图标文件路径\n */\nasync function findAppIconPath(appPath: string): Promise<string | null> {\n const resourcesPath = path.join(appPath, 'Contents', 'Resources');\n \n try {\n // 常见的图标文件名\n const commonIconNames = [\n 'AppIcon.icns',\n 'app.icns',\n 'application.icns',\n 'icon.icns',\n ];\n \n // 先尝试读取 Info.plist 来获取图标文件名\n const infoPlistPath = path.join(appPath, 'Contents', 'Info.plist');\n try {\n const infoPlistContent = await fs.readFile(infoPlistPath, 'utf-8');\n const iconFileMatch = infoPlistContent.match(/<key>CFBundleIconFile<\\/key>\\s*<string>([^<]+)<\\/string>/);\n if (iconFileMatch && iconFileMatch[1]) {\n let iconFileName = iconFileMatch[1].trim();\n if (!iconFileName.endsWith('.icns')) {\n iconFileName += '.icns';\n }\n const iconPath = path.join(resourcesPath, iconFileName);\n try {\n await fs.access(iconPath);\n return iconPath;\n } catch {\n // 继续尝试其他方法\n }\n }\n } catch {\n // Info.plist 读取失败\n }\n \n // 尝试常见文件名\n for (const iconName of commonIconNames) {\n const iconPath = path.join(resourcesPath, iconName);\n try {\n await fs.access(iconPath);\n return iconPath;\n } catch {\n continue;\n }\n }\n \n // 列出 Resources 目录,查找 .icns 文件\n try {\n const entries = await fs.readdir(resourcesPath);\n const icnsFile = entries.find(entry => entry.toLowerCase().endsWith('.icns'));\n if (icnsFile) {\n return path.join(resourcesPath, icnsFile);\n }\n } catch {\n // 目录读取失败\n }\n \n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * 获取应用程序图标(仅 macOS)\n * \n * 使用 sips 命令将 .icns 转换为 PNG,返回 base64 data URL\n */\nexport async function getApplicationIcon(appPath: string): Promise<string | null> {\n if (process.platform !== 'darwin') {\n return null;\n }\n \n // 验证路径\n try {\n const stats = await fs.stat(appPath);\n if (!stats.isDirectory() || !appPath.endsWith('.app')) {\n return null;\n }\n } catch {\n return null;\n }\n \n // 从应用程序包中读取 .icns 文件\n const iconPath = await findAppIconPath(appPath);\n if (!iconPath) {\n return null;\n }\n \n try {\n // 使用 macOS sips 命令转换为 PNG\n const tempPngPath = path.join(os.tmpdir(), `app-icon-${Date.now()}.png`);\n await execAsync(\n `sips -s format png \"${iconPath}\" --out \"${tempPngPath}\" --resampleHeightWidthMax 128`\n );\n \n const pngBuffer = await fs.readFile(tempPngPath);\n \n // 删除临时文件\n try {\n await fs.unlink(tempPngPath);\n } catch {\n // 忽略\n }\n \n const base64 = pngBuffer.toString('base64');\n return `data:image/png;base64,${base64}`;\n } catch {\n return null;\n }\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { getFileType } from '../utils/file-type';\nimport { getFileHash } from '../hash';\nimport { FileType } from '../types';\nimport type { \n ThumbnailDatabase, \n ImageProcessor, \n VideoProcessor,\n ThumbnailServiceOptions \n} from './types';\n\n/** 图片扩展名 */\nconst IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'];\n\n/** 视频扩展名 */\nconst VIDEO_EXTENSIONS = ['.mp4', '.mov', '.avi', '.mkv', '.webm', '.flv', '.wmv'];\n\n/**\n * 缩略图服务\n */\nexport class ThumbnailService {\n private database: ThumbnailDatabase;\n private imageProcessor: ImageProcessor | null;\n private videoProcessor: VideoProcessor | null;\n private urlEncoder: (filePath: string) => string;\n private getApplicationIcon: ((appPath: string) => Promise<string | null>) | null;\n\n constructor(options: ThumbnailServiceOptions) {\n this.database = options.database;\n this.imageProcessor = options.imageProcessor || null;\n this.videoProcessor = options.videoProcessor || null;\n this.urlEncoder = options.urlEncoder || ((p) => `file://${encodeURIComponent(p)}`);\n this.getApplicationIcon = options.getApplicationIcon || null;\n }\n\n /**\n * 获取缓存的缩略图 URL(不生成新的)\n */\n async getCachedThumbnailUrl(filePath: string): Promise<string | null> {\n try {\n const stats = await fs.stat(filePath);\n const fileType = getFileType(filePath, stats);\n \n // 如果是应用程序,获取应用程序图标(macOS)\n if (fileType === FileType.APPLICATION && this.getApplicationIcon) {\n return await this.getApplicationIcon(filePath);\n }\n \n // 只处理图片和视频\n if (fileType !== FileType.IMAGE && fileType !== FileType.VIDEO) {\n return null;\n }\n \n const mtime = stats.mtime.getTime();\n \n // 快速查询缓存(不计算哈希)\n const cachedPath = this.database.getThumbnailPathFast(filePath, mtime);\n if (cachedPath) {\n return this.urlEncoder(cachedPath);\n }\n \n // 没有缓存,异步触发生成(不阻塞)\n getFileHash(filePath).then(fileHash => {\n this.generateThumbnail(filePath, fileHash, mtime).catch(() => {});\n }).catch(() => {});\n \n return null;\n } catch (error) {\n return null;\n }\n }\n\n /**\n * 获取缩略图 URL(如果没有缓存则生成)\n */\n async getThumbnailUrl(filePath: string): Promise<string | null> {\n try {\n const stats = await fs.stat(filePath);\n const fileType = getFileType(filePath, stats);\n \n // 如果是应用程序,获取应用程序图标(macOS)\n if (fileType === FileType.APPLICATION && this.getApplicationIcon) {\n return await this.getApplicationIcon(filePath);\n }\n \n // 只处理图片和视频\n if (fileType !== FileType.IMAGE && fileType !== FileType.VIDEO) {\n return null;\n }\n \n const mtime = stats.mtime.getTime();\n \n // 快速查询缓存(不计算哈希)\n const cachedPath = this.database.getThumbnailPathFast(filePath, mtime);\n if (cachedPath) {\n return this.urlEncoder(cachedPath);\n }\n \n // 缓存未命中,计算哈希并生成缩略图\n const fileHash = await getFileHash(filePath);\n const thumbnailPath = await this.generateThumbnail(filePath, fileHash, mtime);\n if (thumbnailPath) {\n return this.urlEncoder(thumbnailPath);\n }\n \n return null;\n } catch (error) {\n console.error(`Error getting thumbnail for ${filePath}:`, error);\n return null;\n }\n }\n\n /**\n * 生成缩略图\n */\n async generateThumbnail(\n filePath: string,\n fileHash: string,\n mtime: number\n ): Promise<string | null> {\n // 检查缓存\n const cachedPath = this.database.getThumbnailPath(filePath, fileHash, mtime);\n if (cachedPath) {\n return cachedPath;\n }\n\n try {\n const ext = path.extname(filePath).toLowerCase();\n \n // 生成缩略图文件名\n const hashPrefix = fileHash.substring(0, 16);\n const thumbnailFileName = `${hashPrefix}.jpg`;\n const thumbnailPath = path.join(this.database.getCacheDir(), thumbnailFileName);\n\n // 根据文件类型选择不同的生成方法\n if (IMAGE_EXTENSIONS.includes(ext) && this.imageProcessor) {\n await this.imageProcessor.resize(filePath, thumbnailPath, 256);\n } else if (VIDEO_EXTENSIONS.includes(ext) && this.videoProcessor) {\n await this.videoProcessor.screenshot(filePath, thumbnailPath, '00:00:01', '256x256');\n } else {\n return null;\n }\n\n // 保存到数据库\n this.database.saveThumbnail(filePath, fileHash, mtime, thumbnailPath);\n\n return thumbnailPath;\n } catch (error) {\n console.debug(`Error generating thumbnail for ${filePath}:`, error);\n return null;\n }\n }\n\n /**\n * 批量生成缩略图\n */\n async generateThumbnailsBatch(\n files: Array<{ path: string; hash: string; mtime: number }>\n ): Promise<void> {\n await Promise.allSettled(\n files.map(file => this.generateThumbnail(file.path, file.hash, file.mtime))\n );\n }\n\n /**\n * 删除缩略图\n */\n deleteThumbnail(filePath: string): void {\n this.database.deleteThumbnail(filePath);\n }\n\n /**\n * 清理旧缩略图\n */\n cleanupOldThumbnails(): void {\n this.database.cleanupOldThumbnails();\n }\n}\n\n// 全局单例\nlet thumbnailService: ThumbnailService | null = null;\n\n/**\n * 初始化缩略图服务\n */\nexport function initThumbnailService(options: ThumbnailServiceOptions): ThumbnailService {\n thumbnailService = new ThumbnailService(options);\n return thumbnailService;\n}\n\n/**\n * 获取缩略图服务实例\n */\nexport function getThumbnailService(): ThumbnailService | null {\n return thumbnailService;\n}\n","/**\n * SQLite 缩略图数据库实现\n * \n * 需要安装 better-sqlite3:npm install better-sqlite3\n */\n\nimport Database from 'better-sqlite3';\nimport path from 'node:path';\nimport { existsSync, mkdirSync } from 'node:fs';\nimport type { ThumbnailDatabase } from './types';\n\n/**\n * SQLite 缩略图数据库实现\n */\nexport class SqliteThumbnailDatabase implements ThumbnailDatabase {\n private db: Database.Database | null = null;\n private cacheDir: string;\n\n constructor(userDataPath: string) {\n this.cacheDir = path.join(userDataPath, 'cache');\n \n if (!existsSync(this.cacheDir)) {\n mkdirSync(this.cacheDir, { recursive: true });\n }\n }\n\n /**\n * 初始化数据库\n */\n init(): void {\n if (this.db) return;\n\n const dbPath = path.join(this.cacheDir, 'thumbnails.db');\n \n // 使用 WAL 模式提高并发性能,避免锁定问题\n this.db = new Database(dbPath, { \n fileMustExist: false,\n });\n \n // 启用 WAL 模式\n this.db.pragma('journal_mode = WAL');\n\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS thumbnails (\n file_path TEXT PRIMARY KEY,\n file_hash TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n thumbnail_path TEXT NOT NULL,\n created_at INTEGER NOT NULL\n );\n \n CREATE INDEX IF NOT EXISTS idx_file_hash ON thumbnails(file_hash);\n CREATE INDEX IF NOT EXISTS idx_mtime ON thumbnails(mtime);\n `);\n }\n\n getCacheDir(): string {\n return this.cacheDir;\n }\n\n /**\n * 快速查询缩略图(用路径和修改时间,不需要哈希)\n * 比计算文件哈希快很多\n */\n getThumbnailPathFast(filePath: string, mtime: number): string | null {\n if (!this.db) this.init();\n\n const stmt = this.db!.prepare(`\n SELECT thumbnail_path \n FROM thumbnails \n WHERE file_path = ? AND mtime = ?\n `);\n\n const row = stmt.get(filePath, mtime) as { thumbnail_path: string } | undefined;\n\n if (row && existsSync(row.thumbnail_path)) {\n return row.thumbnail_path;\n }\n\n return null;\n }\n\n getThumbnailPath(filePath: string, fileHash: string, mtime: number): string | null {\n if (!this.db) this.init();\n\n const stmt = this.db!.prepare(`\n SELECT thumbnail_path, mtime \n FROM thumbnails \n WHERE file_path = ? AND file_hash = ?\n `);\n\n const row = stmt.get(filePath, fileHash) as { thumbnail_path: string; mtime: number } | undefined;\n\n if (row && row.mtime === mtime && existsSync(row.thumbnail_path)) {\n return row.thumbnail_path;\n }\n\n return null;\n }\n\n saveThumbnail(filePath: string, fileHash: string, mtime: number, thumbnailPath: string): void {\n if (!this.db) this.init();\n\n const stmt = this.db!.prepare(`\n INSERT OR REPLACE INTO thumbnails \n (file_path, file_hash, mtime, thumbnail_path, created_at)\n VALUES (?, ?, ?, ?, ?)\n `);\n\n stmt.run(filePath, fileHash, mtime, thumbnailPath, Date.now());\n }\n\n deleteThumbnail(filePath: string): void {\n if (!this.db) this.init();\n\n const stmt = this.db!.prepare('DELETE FROM thumbnails WHERE file_path = ?');\n stmt.run(filePath);\n }\n\n cleanupOldThumbnails(): void {\n if (!this.db) this.init();\n\n const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;\n const stmt = this.db!.prepare('DELETE FROM thumbnails WHERE created_at < ?');\n stmt.run(thirtyDaysAgo);\n }\n\n close(): void {\n if (this.db) {\n this.db.close();\n this.db = null;\n }\n }\n}\n\n// 单例\nlet thumbnailDb: SqliteThumbnailDatabase | null = null;\n\n/**\n * 创建 SQLite 缩略图数据库\n */\nexport function createSqliteThumbnailDatabase(userDataPath: string): SqliteThumbnailDatabase {\n if (!thumbnailDb) {\n thumbnailDb = new SqliteThumbnailDatabase(userDataPath);\n thumbnailDb.init();\n }\n return thumbnailDb;\n}\n\n/**\n * 获取缩略图数据库实例\n */\nexport function getSqliteThumbnailDatabase(): SqliteThumbnailDatabase | null {\n return thumbnailDb;\n}\n","/**\n * 缩略图处理器工厂函数\n * \n * 需要安装:\n * - sharp: npm install sharp\n * - ffmpeg-static: npm install ffmpeg-static\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { ImageProcessor, VideoProcessor } from './types';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Sharp 类型定义(宽松类型,兼容 sharp 模块)\n */\ninterface SharpInstance {\n resize(width: number, height: number, options?: { fit?: string; withoutEnlargement?: boolean }): SharpInstance;\n jpeg(options?: { quality?: number }): SharpInstance;\n toFile(outputPath: string): Promise<unknown>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype SharpModule = (input: string, options?: any) => SharpInstance;\n\n/**\n * 创建 Sharp 图片处理器\n * \n * @example\n * ```ts\n * import sharp from 'sharp';\n * import { createSharpImageProcessor } from '@huyooo/file-explorer-core';\n * \n * const imageProcessor = createSharpImageProcessor(sharp);\n * ```\n */\nexport function createSharpImageProcessor(sharp: SharpModule): ImageProcessor {\n return {\n async resize(filePath: string, outputPath: string, size: number) {\n await sharp(filePath)\n .resize(size, size, {\n fit: 'inside',\n withoutEnlargement: true,\n })\n .jpeg({ quality: 85 })\n .toFile(outputPath);\n }\n };\n}\n\n/**\n * 创建 FFmpeg 视频处理器(使用 child_process 直接调用 ffmpeg)\n * \n * @param ffmpegPath - ffmpeg 可执行文件路径(来自 ffmpeg-static)\n * \n * @example\n * ```ts\n * import ffmpegStatic from 'ffmpeg-static';\n * import { createFfmpegVideoProcessor } from '@huyooo/file-explorer-core';\n * \n * const videoProcessor = ffmpegStatic \n * ? createFfmpegVideoProcessor(ffmpegStatic) \n * : undefined;\n * ```\n */\nexport function createFfmpegVideoProcessor(ffmpegPath: string): VideoProcessor {\n return {\n async screenshot(filePath: string, outputPath: string, timestamp: string, size: string): Promise<void> {\n // 解析尺寸 \"200x200\" -> \"200:200\"\n const scaleSize = size.replace('x', ':');\n \n await execFileAsync(ffmpegPath, [\n '-i', filePath,\n '-ss', timestamp,\n '-vframes', '1',\n '-vf', `scale=${scaleSize}:force_original_aspect_ratio=decrease`,\n '-y',\n outputPath\n ]);\n }\n };\n}\n"],"mappings":"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","names":["FileType","path","path","path","fs","path","fs","fs","fs","path","fs","path","path","path","fs","fs","path","copyDirectory","fs","path","os","fs","path","IMAGE_EXTENSIONS","VIDEO_EXTENSIONS","fs","path","path","promisify"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/protocol.ts","../src/utils/file-type.ts","../src/utils/formatters.ts","../src/operations/read.ts","../src/operations/write.ts","../src/operations/delete.ts","../src/operations/rename.ts","../src/operations/copy.ts","../src/operations/info.ts","../src/operations/shell.ts","../src/operations/compress.ts","../src/system-paths.ts","../src/search.ts","../src/hash.ts","../src/clipboard.ts","../src/application-icon.ts","../src/watch.ts","../src/thumbnail/service.ts","../src/thumbnail/database.ts","../src/thumbnail/processors.ts","../src/media/format-detector.ts","../src/media/transcoder.ts","../src/media/service.ts"],"sourcesContent":["/**\n * 文件类型枚举\n */\nexport enum FileType {\n FOLDER = 'folder',\n FILE = 'file',\n IMAGE = 'image',\n VIDEO = 'video',\n MUSIC = 'music',\n DOCUMENT = 'document',\n CODE = 'code',\n TEXT = 'text',\n ARCHIVE = 'archive',\n APPLICATION = 'application',\n UNKNOWN = 'unknown'\n}\n\n/**\n * 文件系统项\n */\nexport interface FileItem {\n /** 唯一标识(通常是完整路径) */\n id: string;\n /** 文件名 */\n name: string;\n /** 文件类型 */\n type: FileType;\n /** 文件大小(格式化后的字符串) */\n size?: string;\n /** 修改日期(格式化后的字符串) */\n dateModified?: string;\n /** 文件 URL(用于加载) */\n url?: string;\n /** 缩略图 URL */\n thumbnailUrl?: string;\n /** 子项(仅文件夹) */\n children?: FileItem[];\n}\n\n/**\n * 文件信息\n */\nexport interface FileInfo {\n path: string;\n name: string;\n size: number;\n isFile: boolean;\n isDirectory: boolean;\n createdAt: Date;\n updatedAt: Date;\n extension?: string;\n}\n\n/**\n * 操作结果\n */\nexport interface OperationResult<T = void> {\n success: boolean;\n data?: T;\n error?: string;\n message?: string;\n}\n\n/**\n * 系统路径 ID\n */\nexport type SystemPathId = \n | 'desktop' \n | 'documents' \n | 'downloads' \n | 'pictures' \n | 'music'\n | 'videos'\n | 'applications' \n | 'home' \n | 'root';\n\n/**\n * 平台适配器接口\n * 用于处理平台特定的操作(如 Electron 的 shell.trashItem)\n */\nexport interface PlatformAdapter {\n /** 删除文件到回收站 */\n trashItem?: (path: string) => Promise<void>;\n /** 打开文件 */\n openPath?: (path: string) => Promise<void>;\n /** 使用指定应用打开 */\n openWith?: (path: string, appPath: string) => Promise<void>;\n}\n\n/**\n * 文件操作选项\n */\nexport interface FileOperationOptions {\n /** 平台适配器 */\n adapter?: PlatformAdapter;\n /** 是否自动重命名(当目标存在时) */\n autoRename?: boolean;\n}\n","/**\n * 自定义协议工具模块\n * \n * 统一处理 app:// 协议的编码和解码\n * \n * 协议格式: app://file/path/to/file.jpg\n * - scheme: app\n * - host: file\n * - path: /path/to/file.jpg(路径段编码,保留 /)\n */\n\n/** 协议前缀 */\nexport const APP_PROTOCOL_SCHEME = 'app';\nexport const APP_PROTOCOL_HOST = 'file';\nexport const APP_PROTOCOL_PREFIX = `${APP_PROTOCOL_SCHEME}://${APP_PROTOCOL_HOST}`;\n\n/**\n * 编码文件路径为 app:// 协议 URL\n * 分段编码路径,保留 / 分隔符,符合 URL 标准\n * \n * @param filePath - 文件的绝对路径,如 /Users/name/file.jpg\n * @returns app:// 协议 URL,如 app://file/Users/name/file.jpg\n * \n * @example\n * encodeFileUrl('/Users/name/my file.jpg')\n * // => 'app://file/Users/name/my%20file.jpg'\n */\nexport function encodeFileUrl(filePath: string): string {\n if (!filePath) return '';\n \n // 分割路径,编码每个段,再用 / 连接\n const encodedPath = filePath\n .split('/')\n .map(segment => encodeURIComponent(segment))\n .join('/');\n \n return `${APP_PROTOCOL_PREFIX}${encodedPath}`;\n}\n\n/**\n * 解码 app:// 协议 URL 为文件路径\n * \n * @param url - app:// 协议 URL\n * @returns 文件的绝对路径\n * \n * @example\n * decodeFileUrl('app://file/Users/name/my%20file.jpg')\n * // => '/Users/name/my file.jpg'\n */\nexport function decodeFileUrl(url: string): string {\n if (!url) return '';\n \n // 移除协议前缀\n let path = url;\n if (path.startsWith(APP_PROTOCOL_PREFIX)) {\n path = path.slice(APP_PROTOCOL_PREFIX.length);\n }\n \n // 移除 hash 部分(如视频时间片段 #t=0,104.03)\n const hashIndex = path.indexOf('#');\n if (hashIndex !== -1) {\n path = path.substring(0, hashIndex);\n }\n \n // 移除查询参数\n const queryIndex = path.indexOf('?');\n if (queryIndex !== -1) {\n path = path.substring(0, queryIndex);\n }\n \n // 解码路径\n try {\n return decodeURIComponent(path);\n } catch {\n return path;\n }\n}\n\n/**\n * 检查 URL 是否为 app:// 协议\n */\nexport function isAppProtocolUrl(url: string): boolean {\n return url?.startsWith(APP_PROTOCOL_PREFIX) ?? false;\n}\n","import path from 'node:path';\nimport type { Stats } from 'node:fs';\nimport { FileType } from '../types';\n\n/** 图片扩展名 */\nconst IMAGE_EXTENSIONS = new Set([\n '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.ico', '.tiff', '.tif', '.heic', '.heif'\n]);\n\n/** 视频扩展名 */\nconst VIDEO_EXTENSIONS = new Set([\n '.mp4', '.mov', '.avi', '.mkv', '.wmv', '.flv', '.webm', '.m4v', '.3gp', '.mpeg', '.mpg'\n]);\n\n/** 音频扩展名 */\nconst MUSIC_EXTENSIONS = new Set([\n '.mp3', '.wav', '.flac', '.aac', '.ogg', '.wma', '.m4a', '.aiff', '.alac'\n]);\n\n/** 文档扩展名 */\nconst DOCUMENT_EXTENSIONS = new Set([\n '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.odt', '.ods', '.odp', '.rtf', '.pages', '.numbers', '.key'\n]);\n\n/** 代码扩展名 */\nconst CODE_EXTENSIONS = new Set([\n '.js', '.ts', '.jsx', '.tsx', '.vue', '.svelte',\n '.py', '.rb', '.go', '.rs', '.java', '.kt', '.swift', '.c', '.cpp', '.h', '.hpp',\n '.html', '.css', '.scss', '.sass', '.less',\n '.json', '.yaml', '.yml', '.toml', '.xml',\n '.sh', '.bash', '.zsh', '.fish', '.ps1',\n '.sql', '.graphql', '.prisma'\n]);\n\n/** 文本扩展名 */\nconst TEXT_EXTENSIONS = new Set([\n '.txt', '.md', '.markdown', '.log', '.ini', '.conf', '.cfg', '.env'\n]);\n\n/** 压缩包扩展名 */\nconst ARCHIVE_EXTENSIONS = new Set([\n '.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.xz', '.dmg', '.iso'\n]);\n\n/** 应用程序扩展名 */\nconst APPLICATION_EXTENSIONS = new Set([\n '.app', '.exe', '.msi', '.deb', '.rpm', '.pkg', '.apk', '.ipa'\n]);\n\n/**\n * 根据文件路径和状态获取文件类型\n */\nexport function getFileType(filePath: string, stats: Stats): FileType {\n if (stats.isDirectory()) {\n // 检查是否是 macOS 应用程序包\n if (filePath.endsWith('.app')) {\n return FileType.APPLICATION;\n }\n return FileType.FOLDER;\n }\n\n const ext = path.extname(filePath).toLowerCase();\n\n if (IMAGE_EXTENSIONS.has(ext)) return FileType.IMAGE;\n if (VIDEO_EXTENSIONS.has(ext)) return FileType.VIDEO;\n if (MUSIC_EXTENSIONS.has(ext)) return FileType.MUSIC;\n if (DOCUMENT_EXTENSIONS.has(ext)) return FileType.DOCUMENT;\n if (CODE_EXTENSIONS.has(ext)) return FileType.CODE;\n if (TEXT_EXTENSIONS.has(ext)) return FileType.TEXT;\n if (ARCHIVE_EXTENSIONS.has(ext)) return FileType.ARCHIVE;\n if (APPLICATION_EXTENSIONS.has(ext)) return FileType.APPLICATION;\n\n return FileType.FILE;\n}\n\n/**\n * 判断是否为媒体文件\n */\nexport function isMediaFile(type: FileType): boolean {\n return type === FileType.IMAGE || type === FileType.VIDEO || type === FileType.MUSIC;\n}\n\n/**\n * 判断是否为可预览的文件\n */\nexport function isPreviewable(type: FileType): boolean {\n return type === FileType.IMAGE || type === FileType.VIDEO || type === FileType.MUSIC || \n type === FileType.TEXT || type === FileType.CODE;\n}\n","/**\n * 格式化文件大小\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return '0 B';\n \n const units = ['B', 'KB', 'MB', 'GB', 'TB'];\n const k = 1024;\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n \n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${units[i]}`;\n}\n\n/**\n * 格式化日期\n */\nexport function formatDate(date: Date): string {\n const now = new Date();\n const diff = now.getTime() - date.getTime();\n const days = Math.floor(diff / (1000 * 60 * 60 * 24));\n \n if (days === 0) {\n return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });\n } else if (days === 1) {\n return '昨天';\n } else if (days < 7) {\n return `${days} 天前`;\n } else {\n return date.toLocaleDateString('zh-CN', { \n year: 'numeric', \n month: '2-digit', \n day: '2-digit' \n });\n }\n}\n\n/**\n * 格式化日期时间\n */\nexport function formatDateTime(date: Date): string {\n return date.toLocaleString('zh-CN', {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit'\n });\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { FileItem } from '../types';\nimport { FileType } from '../types';\nimport { getFileType } from '../utils/file-type';\nimport { formatFileSize, formatDate } from '../utils/formatters';\n\n/**\n * URL 编码器(可由外部注入)\n */\nexport type UrlEncoder = (filePath: string) => string;\n\n/** 默认 URL 编码器 */\nconst defaultUrlEncoder: UrlEncoder = (filePath) => `file://${encodeURIComponent(filePath)}`;\n\n/**\n * 读取目录配置\n */\nexport interface ReadDirectoryOptions {\n /** URL 编码器 */\n urlEncoder?: UrlEncoder;\n /** 是否包含隐藏文件 */\n includeHidden?: boolean;\n /** 缩略图 URL 获取器(同步获取缓存的缩略图,没有则返回 null 并异步生成) */\n getThumbnailUrl?: (filePath: string) => Promise<string | null>;\n}\n\n/**\n * 读取目录内容\n */\nexport async function readDirectory(\n dirPath: string, \n options: ReadDirectoryOptions = {}\n): Promise<FileItem[]> {\n const { \n urlEncoder = defaultUrlEncoder,\n includeHidden = false,\n getThumbnailUrl\n } = options;\n\n try {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n const items: FileItem[] = [];\n\n for (const entry of entries) {\n // 跳过隐藏文件(以 . 开头)\n if (!includeHidden && entry.name.startsWith('.')) {\n continue;\n }\n\n const fullPath = path.join(dirPath, entry.name);\n\n try {\n // 使用 lstat 避免跟随符号链接\n let stats;\n try {\n stats = await fs.lstat(fullPath);\n } catch {\n try {\n stats = await fs.stat(fullPath);\n } catch (statError: unknown) {\n const err = statError as NodeJS.ErrnoException;\n if (err.code !== 'ENOENT') {\n console.warn(`Cannot access file ${fullPath}:`, err.message);\n }\n continue;\n }\n }\n\n const fileType = getFileType(fullPath, stats);\n const fileUrl = urlEncoder(fullPath);\n\n const item: FileItem = {\n id: fullPath,\n name: entry.name,\n type: fileType,\n dateModified: formatDate(stats.mtime),\n url: fileUrl,\n };\n\n if (stats.isDirectory()) {\n item.children = []; // 延迟加载\n } else {\n item.size = formatFileSize(stats.size);\n\n // 同步获取缩略图 URL(如果已缓存)\n if (getThumbnailUrl && (fileType === FileType.IMAGE || fileType === FileType.VIDEO)) {\n const thumbUrl = await getThumbnailUrl(fullPath);\n if (thumbUrl) {\n item.thumbnailUrl = thumbUrl;\n }\n }\n }\n\n items.push(item);\n } catch (itemError: unknown) {\n const err = itemError as NodeJS.ErrnoException;\n if (err.code !== 'ENOENT') {\n console.warn(`Error processing file ${fullPath}:`, err.message);\n }\n continue;\n }\n }\n\n // 排序:文件夹优先,然后按名称\n items.sort((a, b) => {\n if (a.type === FileType.FOLDER && b.type !== FileType.FOLDER) return -1;\n if (a.type !== FileType.FOLDER && b.type === FileType.FOLDER) return 1;\n return a.name.localeCompare(b.name, 'zh-CN');\n });\n\n return items;\n } catch (error) {\n console.error(`Error reading directory ${dirPath}:`, error);\n return [];\n }\n}\n\n/**\n * 读取文件内容\n */\nexport async function readFileContent(filePath: string): Promise<string> {\n return await fs.readFile(filePath, 'utf-8');\n}\n\n/**\n * 读取图片为 Base64\n */\nexport async function readImageAsBase64(imagePath: string): Promise<{\n success: boolean;\n base64?: string;\n mimeType?: string;\n error?: string;\n}> {\n try {\n // 处理各种 URL 格式,解码为实际文件路径\n let actualPath = imagePath;\n if (imagePath.startsWith('app://file')) {\n // 移除 app://file 前缀,然后解码\n const urlPath = imagePath.slice('app://file'.length);\n actualPath = decodeURIComponent(urlPath);\n } else if (imagePath.startsWith('file://')) {\n actualPath = decodeURIComponent(imagePath.replace('file://', ''));\n }\n\n const stats = await fs.stat(actualPath);\n if (!stats.isFile()) {\n return { success: false, error: `路径不是文件: ${actualPath}` };\n }\n\n const buffer = await fs.readFile(actualPath);\n const base64 = buffer.toString('base64');\n const ext = path.extname(actualPath).toLowerCase().slice(1);\n \n const mimeTypes: Record<string, string> = {\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n bmp: 'image/bmp',\n svg: 'image/svg+xml',\n };\n \n const mimeType = mimeTypes[ext] || 'image/jpeg';\n return { success: true, base64, mimeType };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { OperationResult } from '../types';\n\n/**\n * 写入文件内容\n */\nexport async function writeFileContent(\n filePath: string, \n content: string\n): Promise<OperationResult> {\n try {\n await fs.writeFile(filePath, content, 'utf-8');\n return { success: true };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 创建文件夹(自动处理同名文件夹)\n */\nexport async function createFolder(\n folderPath: string\n): Promise<OperationResult<{ finalPath: string }>> {\n try {\n // 检查是否存在\n try {\n await fs.access(folderPath);\n // 存在,需要自动重命名\n } catch {\n // 不存在,直接创建\n await fs.mkdir(folderPath, { recursive: true });\n return { success: true, data: { finalPath: folderPath } };\n }\n\n // 自动重命名\n const dir = path.dirname(folderPath);\n const baseName = path.basename(folderPath);\n let counter = 2;\n let finalPath = path.join(dir, `${baseName} ${counter}`);\n\n while (true) {\n try {\n await fs.access(finalPath);\n counter++;\n finalPath = path.join(dir, `${baseName} ${counter}`);\n } catch {\n break;\n }\n }\n\n await fs.mkdir(finalPath, { recursive: true });\n return { success: true, data: { finalPath } };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 创建文件(自动处理同名文件)\n */\nexport async function createFile(\n filePath: string,\n content: string = ''\n): Promise<OperationResult<{ finalPath: string }>> {\n try {\n // 确保父目录存在\n const dir = path.dirname(filePath);\n await fs.mkdir(dir, { recursive: true });\n\n // 检查是否存在\n try {\n await fs.access(filePath);\n // 存在,需要自动重命名\n } catch {\n // 不存在,直接创建\n await fs.writeFile(filePath, content, 'utf-8');\n return { success: true, data: { finalPath: filePath } };\n }\n\n // 自动重命名(保留扩展名)\n const dirname = path.dirname(filePath);\n const ext = path.extname(filePath);\n const basename = path.basename(filePath, ext);\n let counter = 2;\n let finalPath = path.join(dirname, `${basename} ${counter}${ext}`);\n\n while (true) {\n try {\n await fs.access(finalPath);\n counter++;\n finalPath = path.join(dirname, `${basename} ${counter}${ext}`);\n } catch {\n break;\n }\n }\n\n await fs.writeFile(finalPath, content, 'utf-8');\n return { success: true, data: { finalPath } };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n","import { promises as fs } from 'node:fs';\nimport type { OperationResult, PlatformAdapter } from '../types';\n\n/**\n * 删除文件选项\n */\nexport interface DeleteOptions {\n /** 平台适配器(用于移到回收站) */\n adapter?: PlatformAdapter;\n /** 是否使用回收站(默认 true) */\n useTrash?: boolean;\n /** 删除后回调(用于清理缩略图等) */\n onDeleted?: (path: string) => void;\n}\n\n/**\n * 删除文件/文件夹\n * \n * 如果提供了 adapter.trashItem,则移到回收站\n * 否则直接删除(危险操作)\n */\nexport async function deleteFiles(\n paths: string[],\n options: DeleteOptions = {}\n): Promise<OperationResult> {\n const { adapter, useTrash = true, onDeleted } = options;\n\n try {\n for (const filePath of paths) {\n if (useTrash && adapter?.trashItem) {\n // 使用平台特定的回收站功能\n await adapter.trashItem(filePath);\n } else {\n // 直接删除(谨慎使用)\n const stats = await fs.stat(filePath);\n if (stats.isDirectory()) {\n await fs.rm(filePath, { recursive: true, force: true });\n } else {\n await fs.unlink(filePath);\n }\n }\n\n // 回调\n onDeleted?.(filePath);\n }\n\n return { \n success: true, \n message: useTrash ? '文件已移动到回收站' : '文件已删除' \n };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n","import { promises as fs } from 'node:fs';\nimport type { OperationResult } from '../types';\n\n/**\n * 重命名选项\n */\nexport interface RenameOptions {\n /** 重命名后回调 */\n onRenamed?: (oldPath: string, newPath: string) => void;\n}\n\n/**\n * 重命名文件/文件夹\n */\nexport async function renameFile(\n oldPath: string,\n newPath: string,\n options: RenameOptions = {}\n): Promise<OperationResult> {\n const { onRenamed } = options;\n\n try {\n await fs.rename(oldPath, newPath);\n onRenamed?.(oldPath, newPath);\n return { success: true };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { OperationResult } from '../types';\n\n/**\n * 复制文件到目标目录\n */\nexport async function copyFiles(\n sourcePaths: string[],\n targetDir: string\n): Promise<OperationResult<{ copiedPaths: string[] }>> {\n try {\n const copiedPaths: string[] = [];\n\n for (const sourcePath of sourcePaths) {\n const fileName = path.basename(sourcePath);\n let destPath = path.join(targetDir, fileName);\n let counter = 1;\n\n // 自动重命名\n while (true) {\n try {\n await fs.access(destPath);\n const ext = path.extname(fileName);\n const baseName = path.basename(fileName, ext);\n destPath = path.join(targetDir, `${baseName} ${++counter}${ext}`);\n } catch {\n break;\n }\n }\n\n // 复制\n const stats = await fs.stat(sourcePath);\n if (stats.isDirectory()) {\n await copyDirectory(sourcePath, destPath);\n } else {\n await fs.copyFile(sourcePath, destPath);\n }\n\n copiedPaths.push(destPath);\n }\n\n return { success: true, data: { copiedPaths } };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 移动文件到目标目录\n */\nexport async function moveFiles(\n sourcePaths: string[],\n targetDir: string\n): Promise<OperationResult<{ movedPaths: string[] }>> {\n try {\n const movedPaths: string[] = [];\n\n for (const sourcePath of sourcePaths) {\n const fileName = path.basename(sourcePath);\n let destPath = path.join(targetDir, fileName);\n let counter = 1;\n\n // 自动重命名\n while (true) {\n try {\n await fs.access(destPath);\n const ext = path.extname(fileName);\n const baseName = path.basename(fileName, ext);\n destPath = path.join(targetDir, `${baseName} ${++counter}${ext}`);\n } catch {\n break;\n }\n }\n\n // 移动(使用 rename,如果跨磁盘则先复制后删除)\n try {\n await fs.rename(sourcePath, destPath);\n } catch {\n const stats = await fs.stat(sourcePath);\n if (stats.isDirectory()) {\n await copyDirectory(sourcePath, destPath);\n } else {\n await fs.copyFile(sourcePath, destPath);\n }\n await fs.rm(sourcePath, { recursive: true, force: true });\n }\n\n movedPaths.push(destPath);\n }\n\n return { success: true, data: { movedPaths } };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 递归复制目录\n */\nasync function copyDirectory(source: string, dest: string): Promise<void> {\n await fs.mkdir(dest, { recursive: true });\n const entries = await fs.readdir(source, { withFileTypes: true });\n\n for (const entry of entries) {\n const sourcePath = path.join(source, entry.name);\n const destPath = path.join(dest, entry.name);\n\n if (entry.isDirectory()) {\n await copyDirectory(sourcePath, destPath);\n } else {\n await fs.copyFile(sourcePath, destPath);\n }\n }\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { FileInfo, OperationResult } from '../types';\n\n/**\n * 获取文件信息\n */\nexport async function getFileInfo(\n filePath: string\n): Promise<OperationResult<FileInfo>> {\n try {\n const stats = await fs.stat(filePath);\n const name = path.basename(filePath);\n const ext = path.extname(name);\n\n return {\n success: true,\n data: {\n path: filePath,\n name,\n size: stats.size,\n isFile: stats.isFile(),\n isDirectory: stats.isDirectory(),\n createdAt: stats.birthtime,\n updatedAt: stats.mtime,\n extension: ext || undefined,\n },\n };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 检查文件/目录是否存在\n */\nexport async function exists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * 检查是否为目录\n */\nexport async function isDirectory(filePath: string): Promise<boolean> {\n try {\n const stats = await fs.stat(filePath);\n return stats.isDirectory();\n } catch {\n return false;\n }\n}\n","/**\n * Shell 操作\n * \n * 系统级操作:显示简介、在新窗口打开等\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport * as path from 'node:path';\n\nconst execAsync = promisify(exec);\n\n/** 获取当前平台 */\nfunction getPlatform(): 'mac' | 'windows' | 'linux' {\n switch (process.platform) {\n case 'darwin': return 'mac';\n case 'win32': return 'windows';\n default: return 'linux';\n }\n}\n\n/**\n * 显示文件/文件夹的系统属性窗口\n * \n * - macOS: 打开 Finder 的\"显示简介\"窗口\n * - Windows: 打开\"属性\"窗口\n * - Linux: 尝试使用文件管理器的属性功能\n */\nexport async function showFileInfo(filePath: string): Promise<{ success: boolean; error?: string }> {\n const platform = getPlatform();\n \n try {\n switch (platform) {\n case 'mac': {\n // 使用 AppleScript 打开 Finder 的\"显示简介\"窗口\n const script = `tell application \"Finder\"\n activate\n set theFile to POSIX file \"${filePath}\" as alias\n open information window of theFile\n end tell`;\n await execAsync(`osascript -e '${script}'`);\n break;\n }\n \n case 'windows': {\n // 使用 PowerShell 调用 Shell API 打开属性窗口\n // 需要转义路径中的特殊字符\n const escapedPath = filePath.replace(/'/g, \"''\");\n const psScript = `\n $shell = New-Object -ComObject Shell.Application\n $folder = $shell.Namespace((Split-Path '${escapedPath}'))\n $item = $folder.ParseName((Split-Path '${escapedPath}' -Leaf))\n $item.InvokeVerb('properties')\n `;\n await execAsync(`powershell -Command \"${psScript.replace(/\\n/g, ' ')}\"`);\n break;\n }\n \n case 'linux': {\n // Linux 下尝试使用常见的文件管理器\n // 优先尝试 nautilus (GNOME), dolphin (KDE), thunar (XFCE)\n const commands = [\n `nautilus --select \"${filePath}\" && nautilus -q`, // GNOME\n `dolphin --select \"${filePath}\"`, // KDE\n `thunar --quit && thunar \"${path.dirname(filePath)}\"`, // XFCE\n `xdg-open \"${path.dirname(filePath)}\"`, // 通用\n ];\n \n let lastError: Error | null = null;\n for (const cmd of commands) {\n try {\n await execAsync(cmd);\n return { success: true };\n } catch (e) {\n lastError = e as Error;\n }\n }\n throw lastError || new Error('No file manager found');\n }\n }\n \n return { success: true };\n } catch (error) {\n return { \n success: false, \n error: error instanceof Error ? error.message : String(error) \n };\n }\n}\n\n/**\n * 在系统文件管理器中显示文件(选中状态)\n * \n * 注意:这个功能在 Electron 中可以用 shell.showItemInFolder 实现\n * 这里提供一个纯 Node.js 的替代实现\n */\nexport async function revealInFileManager(filePath: string): Promise<{ success: boolean; error?: string }> {\n const platform = getPlatform();\n \n try {\n switch (platform) {\n case 'mac': {\n await execAsync(`open -R \"${filePath}\"`);\n break;\n }\n \n case 'windows': {\n await execAsync(`explorer.exe /select,\"${filePath}\"`);\n break;\n }\n \n case 'linux': {\n // 使用 xdg-open 打开文件所在目录\n await execAsync(`xdg-open \"${path.dirname(filePath)}\"`);\n break;\n }\n }\n \n return { success: true };\n } catch (error) {\n return { \n success: false, \n error: error instanceof Error ? error.message : String(error) \n };\n }\n}\n\n/**\n * 在终端中打开目录\n * macOS: 优先 iTerm2,回退到 Terminal.app\n */\nexport async function openInTerminal(dirPath: string): Promise<{ success: boolean; error?: string }> {\n const platform = getPlatform();\n \n try {\n switch (platform) {\n case 'mac': {\n // 优先尝试 iTerm2\n try {\n const itermScript = `tell application \"iTerm\"\n activate\n try\n set newWindow to (create window with default profile)\n tell current session of newWindow\n write text \"cd '${dirPath}'\"\n end tell\n on error\n tell current window\n create tab with default profile\n tell current session\n write text \"cd '${dirPath}'\"\n end tell\n end tell\n end try\n end tell`;\n await execAsync(`osascript -e '${itermScript}'`);\n } catch {\n // 回退到 Terminal.app\n const terminalScript = `tell application \"Terminal\"\n activate\n do script \"cd '${dirPath}'\"\n end tell`;\n await execAsync(`osascript -e '${terminalScript}'`);\n }\n break;\n }\n \n case 'windows': {\n // 打开 Windows Terminal 或 cmd\n try {\n // 优先尝试 Windows Terminal\n await execAsync(`wt -d \"${dirPath}\"`);\n } catch {\n // 回退到 cmd\n await execAsync(`start cmd /K \"cd /d ${dirPath}\"`);\n }\n break;\n }\n \n case 'linux': {\n // 尝试常见的终端\n const terminals = [\n `gnome-terminal --working-directory=\"${dirPath}\"`,\n `konsole --workdir \"${dirPath}\"`,\n `xfce4-terminal --working-directory=\"${dirPath}\"`,\n `xterm -e \"cd '${dirPath}' && $SHELL\"`,\n ];\n \n let lastError: Error | null = null;\n for (const cmd of terminals) {\n try {\n await execAsync(cmd);\n return { success: true };\n } catch (e) {\n lastError = e as Error;\n }\n }\n throw lastError || new Error('No terminal found');\n }\n }\n \n return { success: true };\n } catch (error) {\n return { \n success: false, \n error: error instanceof Error ? error.message : String(error) \n };\n }\n}\n\n/**\n * 通过 Cursor 打开\n * 优先 Cursor,回退到 VSCode\n */\nexport async function openInEditor(targetPath: string): Promise<{ success: boolean; error?: string }> {\n const platform = getPlatform();\n \n try {\n // 尝试的编辑器命令列表(按优先级)\n const editors = platform === 'mac' \n ? [\n // macOS: 优先 Cursor,回退到 VSCode\n `open -a \"Cursor\" \"${targetPath}\"`,\n `open -a \"Visual Studio Code\" \"${targetPath}\"`,\n `code \"${targetPath}\"`,\n ]\n : platform === 'windows'\n ? [\n // Windows\n `cursor \"${targetPath}\"`,\n `code \"${targetPath}\"`,\n ]\n : [\n // Linux\n `cursor \"${targetPath}\"`,\n `code \"${targetPath}\"`,\n ];\n \n let lastError: Error | null = null;\n for (const cmd of editors) {\n try {\n await execAsync(cmd);\n return { success: true };\n } catch (e) {\n lastError = e as Error;\n }\n }\n throw lastError || new Error('No editor found');\n } catch (error) {\n return { \n success: false, \n error: error instanceof Error ? error.message : String(error) \n };\n }\n}\n\n","/**\n * 压缩/解压操作\n * \n * 支持格式:zip, tar, tar.gz (tgz), tar.bz2\n */\n\nimport * as compressing from 'compressing';\nimport * as path from 'node:path';\nimport * as fs from 'node:fs';\nimport { promises as fsPromises } from 'node:fs';\nimport { pipeline } from 'node:stream/promises';\n\n/** 压缩格式 */\nexport type CompressFormat = 'zip' | 'tar' | 'tgz' | 'tarbz2';\n\n/** 压缩级别 */\nexport type CompressLevel = 'fast' | 'normal' | 'best';\n\n/** 压缩选项 */\nexport interface CompressOptions {\n /** 输出格式 */\n format: CompressFormat;\n /** 压缩级别 */\n level?: CompressLevel;\n /** 输出文件名(不含路径) */\n outputName: string;\n /** 输出目录 */\n outputDir: string;\n /** 压缩后删除源文件 */\n deleteSource?: boolean;\n}\n\n/** 解压选项 */\nexport interface ExtractOptions {\n /** 目标目录 */\n targetDir: string;\n /** 解压后删除压缩包 */\n deleteArchive?: boolean;\n}\n\n/** 进度信息 */\nexport interface CompressProgress {\n /** 当前文件 */\n currentFile: string;\n /** 已处理文件数 */\n processedCount: number;\n /** 总文件数 */\n totalCount: number;\n /** 进度百分比 */\n percent: number;\n}\n\n/** 操作结果 */\nexport interface CompressResult {\n success: boolean;\n /** 输出路径 */\n outputPath?: string;\n error?: string;\n}\n\n/** 进度回调 */\nexport type ProgressCallback = (progress: CompressProgress) => void;\n\n/**\n * 压缩级别映射到 zlib 级别\n */\nfunction getLevelValue(level: CompressLevel = 'normal'): number {\n switch (level) {\n case 'fast': return 1;\n case 'normal': return 6;\n case 'best': return 9;\n default: return 6;\n }\n}\n\n/**\n * 根据格式获取文件扩展名\n */\nfunction getExtension(format: CompressFormat): string {\n switch (format) {\n case 'zip': return '.zip';\n case 'tar': return '.tar';\n case 'tgz': return '.tar.gz';\n case 'tarbz2': return '.tar.bz2';\n default: return '.zip';\n }\n}\n\n/**\n * 根据文件扩展名检测格式\n */\nexport function detectArchiveFormat(filePath: string): CompressFormat | null {\n const lowerPath = filePath.toLowerCase();\n if (lowerPath.endsWith('.zip')) return 'zip';\n if (lowerPath.endsWith('.tar.gz') || lowerPath.endsWith('.tgz')) return 'tgz';\n if (lowerPath.endsWith('.tar.bz2') || lowerPath.endsWith('.tbz2')) return 'tarbz2';\n if (lowerPath.endsWith('.tar')) return 'tar';\n return null;\n}\n\n/**\n * 判断文件是否为支持的压缩文件\n */\nexport function isArchiveFile(filePath: string): boolean {\n return detectArchiveFormat(filePath) !== null;\n}\n\n/**\n * 递归获取目录下所有文件\n */\nasync function getAllFiles(dirPath: string): Promise<string[]> {\n const files: string[] = [];\n const entries = await fsPromises.readdir(dirPath, { withFileTypes: true });\n \n for (const entry of entries) {\n const fullPath = path.join(dirPath, entry.name);\n if (entry.isDirectory()) {\n files.push(...await getAllFiles(fullPath));\n } else {\n files.push(fullPath);\n }\n }\n \n return files;\n}\n\n/**\n * 统计要压缩的文件总数\n */\nasync function countFiles(sources: string[]): Promise<number> {\n let count = 0;\n \n for (const source of sources) {\n const stats = await fsPromises.stat(source);\n if (stats.isDirectory()) {\n const files = await getAllFiles(source);\n count += files.length;\n } else {\n count += 1;\n }\n }\n \n return count;\n}\n\n/**\n * 压缩文件/目录\n */\nexport async function compressFiles(\n sources: string[],\n options: CompressOptions,\n onProgress?: ProgressCallback\n): Promise<CompressResult> {\n try {\n const { format, level = 'normal', outputName, outputDir, deleteSource } = options;\n \n // 确保输出文件名有正确的扩展名\n const ext = getExtension(format);\n const finalName = outputName.endsWith(ext) ? outputName : outputName + ext;\n const outputPath = path.join(outputDir, finalName);\n \n // 确保输出目录存在\n await fsPromises.mkdir(outputDir, { recursive: true });\n \n // 统计文件总数\n const totalCount = await countFiles(sources);\n let processedCount = 0;\n \n // 根据格式选择压缩方式\n // 注意:Stream 构造函数不接受参数,压缩级别只能用于 FileStream\n switch (format) {\n case 'zip': {\n const stream = new compressing.zip.Stream();\n \n for (const source of sources) {\n const stats = await fsPromises.stat(source);\n const baseName = path.basename(source);\n \n if (stats.isDirectory()) {\n // 添加目录\n const files = await getAllFiles(source);\n for (const file of files) {\n const relativePath = path.relative(path.dirname(source), file);\n stream.addEntry(file, { relativePath });\n processedCount++;\n onProgress?.({\n currentFile: path.basename(file),\n processedCount,\n totalCount,\n percent: Math.round((processedCount / totalCount) * 100),\n });\n }\n } else {\n // 添加文件\n stream.addEntry(source, { relativePath: baseName });\n processedCount++;\n onProgress?.({\n currentFile: baseName,\n processedCount,\n totalCount,\n percent: Math.round((processedCount / totalCount) * 100),\n });\n }\n }\n \n // 写入文件\n const destStream = fs.createWriteStream(outputPath);\n await pipeline(stream, destStream);\n break;\n }\n \n case 'tar': {\n const stream = new compressing.tar.Stream();\n \n for (const source of sources) {\n const stats = await fsPromises.stat(source);\n const baseName = path.basename(source);\n \n if (stats.isDirectory()) {\n const files = await getAllFiles(source);\n for (const file of files) {\n const relativePath = path.relative(path.dirname(source), file);\n stream.addEntry(file, { relativePath });\n processedCount++;\n onProgress?.({\n currentFile: path.basename(file),\n processedCount,\n totalCount,\n percent: Math.round((processedCount / totalCount) * 100),\n });\n }\n } else {\n stream.addEntry(source, { relativePath: baseName });\n processedCount++;\n onProgress?.({\n currentFile: baseName,\n processedCount,\n totalCount,\n percent: Math.round((processedCount / totalCount) * 100),\n });\n }\n }\n \n const destStream = fs.createWriteStream(outputPath);\n await pipeline(stream, destStream);\n break;\n }\n \n case 'tgz': {\n const stream = new compressing.tgz.Stream();\n \n for (const source of sources) {\n const stats = await fsPromises.stat(source);\n const baseName = path.basename(source);\n \n if (stats.isDirectory()) {\n const files = await getAllFiles(source);\n for (const file of files) {\n const relativePath = path.relative(path.dirname(source), file);\n stream.addEntry(file, { relativePath });\n processedCount++;\n onProgress?.({\n currentFile: path.basename(file),\n processedCount,\n totalCount,\n percent: Math.round((processedCount / totalCount) * 100),\n });\n }\n } else {\n stream.addEntry(source, { relativePath: baseName });\n processedCount++;\n onProgress?.({\n currentFile: baseName,\n processedCount,\n totalCount,\n percent: Math.round((processedCount / totalCount) * 100),\n });\n }\n }\n \n const destStream = fs.createWriteStream(outputPath);\n await pipeline(stream, destStream);\n break;\n }\n \n case 'tarbz2': {\n // compressing 不直接支持 tar.bz2,但我们可以用 tar + 外部 bzip2\n // 暂时降级为 tgz\n console.warn('tar.bz2 format not fully supported, using tgz instead');\n const tgzStream = new compressing.tgz.Stream();\n \n for (const source of sources) {\n const stats = await fsPromises.stat(source);\n const baseName = path.basename(source);\n \n if (stats.isDirectory()) {\n const files = await getAllFiles(source);\n for (const file of files) {\n const relativePath = path.relative(path.dirname(source), file);\n tgzStream.addEntry(file, { relativePath });\n processedCount++;\n onProgress?.({\n currentFile: path.basename(file),\n processedCount,\n totalCount,\n percent: Math.round((processedCount / totalCount) * 100),\n });\n }\n } else {\n tgzStream.addEntry(source, { relativePath: baseName });\n processedCount++;\n onProgress?.({\n currentFile: baseName,\n processedCount,\n totalCount,\n percent: Math.round((processedCount / totalCount) * 100),\n });\n }\n }\n \n const destStream = fs.createWriteStream(outputPath.replace('.tar.bz2', '.tar.gz'));\n await pipeline(tgzStream, destStream);\n break;\n }\n }\n \n // 删除源文件\n if (deleteSource) {\n for (const source of sources) {\n const stats = await fsPromises.stat(source);\n if (stats.isDirectory()) {\n await fsPromises.rm(source, { recursive: true });\n } else {\n await fsPromises.unlink(source);\n }\n }\n }\n \n return { success: true, outputPath };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * 解压文件\n */\nexport async function extractArchive(\n archivePath: string,\n options: ExtractOptions,\n onProgress?: ProgressCallback\n): Promise<CompressResult> {\n try {\n const { targetDir, deleteArchive } = options;\n \n // 检测格式\n const format = detectArchiveFormat(archivePath);\n if (!format) {\n return { success: false, error: '不支持的压缩格式' };\n }\n \n // 确保目标目录存在\n await fsPromises.mkdir(targetDir, { recursive: true });\n \n // 通知开始\n onProgress?.({\n currentFile: path.basename(archivePath),\n processedCount: 0,\n totalCount: 1,\n percent: 0,\n });\n \n // 根据格式解压\n switch (format) {\n case 'zip':\n await compressing.zip.uncompress(archivePath, targetDir);\n break;\n case 'tar':\n await compressing.tar.uncompress(archivePath, targetDir);\n break;\n case 'tgz':\n await compressing.tgz.uncompress(archivePath, targetDir);\n break;\n case 'tarbz2':\n // compressing 不支持 bz2,尝试作为 tgz 处理\n console.warn('tar.bz2 format not fully supported');\n return { success: false, error: 'tar.bz2 格式暂不支持' };\n }\n \n // 通知完成\n onProgress?.({\n currentFile: path.basename(archivePath),\n processedCount: 1,\n totalCount: 1,\n percent: 100,\n });\n \n // 删除压缩包\n if (deleteArchive) {\n await fsPromises.unlink(archivePath);\n }\n \n return { success: true, outputPath: targetDir };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n","import path from 'node:path';\nimport os from 'node:os';\nimport type { SystemPathId } from './types';\n\nconst platform = process.platform;\n\n/**\n * 获取系统路径\n */\nexport function getSystemPath(pathId: SystemPathId): string | null {\n const homeDir = os.homedir();\n\n switch (pathId) {\n case 'desktop':\n return path.join(homeDir, 'Desktop');\n case 'documents':\n return path.join(homeDir, 'Documents');\n case 'downloads':\n return path.join(homeDir, 'Downloads');\n case 'pictures':\n return path.join(homeDir, 'Pictures');\n case 'music':\n return path.join(homeDir, 'Music');\n case 'videos':\n return platform === 'darwin'\n ? path.join(homeDir, 'Movies')\n : path.join(homeDir, 'Videos');\n case 'applications':\n return platform === 'darwin'\n ? '/Applications'\n : platform === 'win32'\n ? process.env.ProgramFiles || 'C:\\\\Program Files'\n : '/usr/share/applications';\n case 'home':\n return homeDir;\n case 'root':\n return platform === 'darwin' ? '/' : platform === 'win32' ? 'C:\\\\' : '/';\n default:\n return null;\n }\n}\n\n/**\n * 获取所有系统路径\n */\nexport function getAllSystemPaths(): Record<SystemPathId, string | null> {\n const pathIds: SystemPathId[] = [\n 'desktop', 'documents', 'downloads', 'pictures', \n 'music', 'videos', 'applications', 'home', 'root'\n ];\n\n const result = {} as Record<SystemPathId, string | null>;\n for (const id of pathIds) {\n result[id] = getSystemPath(id);\n }\n return result;\n}\n\n/**\n * 获取用户主目录\n */\nexport function getHomeDirectory(): string {\n return os.homedir();\n}\n\n/**\n * 获取当前平台\n */\nexport function getPlatform(): NodeJS.Platform {\n return process.platform;\n}\n","import { fdir } from 'fdir';\nimport path from 'node:path';\nimport { promises as fs } from 'node:fs';\n\n/**\n * 将通配符模式转为正则表达式\n */\nfunction patternToRegex(pattern: string): RegExp {\n return new RegExp(pattern.replace(/\\*/g, '.*'), 'i');\n}\n\n/**\n * 使用 fdir 快速搜索文件和文件夹\n * fdir 可以在 1 秒内扫描 100 万个文件\n * \n * @param searchPath 搜索路径\n * @param pattern 搜索模式(支持 * 通配符,忽略大小写)\n * @param maxDepth 最大递归深度\n */\nexport async function searchFiles(\n searchPath: string,\n pattern?: string,\n maxDepth?: number\n): Promise<string[]> {\n const builder = new fdir()\n .withFullPaths()\n .withDirs(); // 包含文件夹\n \n // maxDepth 为 0 或 undefined 时不限制深度\n if (maxDepth && maxDepth > 0) {\n builder.withMaxDepth(maxDepth);\n }\n \n const api = builder.crawl(searchPath);\n\n const files = await api.withPromise();\n\n if (pattern) {\n // 通配符转正则,忽略大小写\n const regex = patternToRegex(pattern);\n return (files as string[]).filter((file: string) => regex.test(path.basename(file)));\n }\n\n return files as string[];\n}\n\n/**\n * 流式搜索文件(逐层搜索,边搜边返回)\n * \n * @param searchPath 搜索路径\n * @param pattern 搜索模式\n * @param onResults 找到结果时的回调\n * @param maxResults 最大结果数\n */\nexport async function searchFilesStream(\n searchPath: string,\n pattern: string,\n onResults: (paths: string[], done: boolean) => void,\n maxResults: number = 100\n): Promise<void> {\n const regex = patternToRegex(pattern);\n const results: string[] = [];\n let stopped = false;\n \n // 递归搜索目录\n async function searchDir(dirPath: string): Promise<void> {\n if (stopped) return;\n \n try {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n const matched: string[] = [];\n const subdirs: string[] = [];\n \n for (const entry of entries) {\n if (stopped) return;\n \n const fullPath = path.join(dirPath, entry.name);\n \n // 检查是否匹配\n if (regex.test(entry.name)) {\n matched.push(fullPath);\n results.push(fullPath);\n \n // 达到最大数量时停止\n if (results.length >= maxResults) {\n stopped = true;\n onResults(matched, true);\n return;\n }\n }\n \n // 收集子目录\n if (entry.isDirectory()) {\n subdirs.push(fullPath);\n }\n }\n \n // 如果有匹配,立即推送\n if (matched.length > 0) {\n onResults(matched, false);\n }\n \n // 递归搜索子目录\n for (const subdir of subdirs) {\n await searchDir(subdir);\n }\n } catch {\n // 忽略无权限的目录\n }\n }\n \n await searchDir(searchPath);\n \n // 搜索完成\n if (!stopped) {\n onResults([], true);\n }\n}\n\n/**\n * 同步搜索(用于小目录)\n */\nexport function searchFilesSync(\n searchPath: string,\n pattern?: string,\n maxDepth?: number\n): string[] {\n const builder = new fdir()\n .withFullPaths()\n .withDirs(); // 包含文件夹\n \n // maxDepth 为 0 或 undefined 时不限制深度\n if (maxDepth && maxDepth > 0) {\n builder.withMaxDepth(maxDepth);\n }\n \n const api = builder.crawl(searchPath);\n const files = api.sync();\n\n if (pattern) {\n const regex = new RegExp(pattern.replace(/\\*/g, '.*'), 'i');\n return (files as string[]).filter((file: string) => regex.test(path.basename(file)));\n }\n\n return files as string[];\n}\n","import { xxhash64 } from 'hash-wasm';\nimport { stat } from 'node:fs/promises';\nimport type { Stats } from 'node:fs';\n\n/**\n * 计算文件的快速 hash(使用文件大小和修改时间)\n * 使用 xxHash64 算法,专为非加密场景设计,性能极快\n * 这比计算完整文件 hash 快得多,适合用于缓存判断\n * \n * @param filePath - 文件路径\n * @param stats - 可选的文件状态(如果已获取,避免重复调用 stat)\n */\nexport async function getFileHash(filePath: string, stats?: Stats): Promise<string> {\n // 如果已经提供了 stats,直接使用,避免重复调用 stat()\n const fileStats = stats || await stat(filePath);\n // 使用文件大小、修改时间和路径的组合\n const hashInput = `${filePath}:${fileStats.size}:${fileStats.mtime.getTime()}`;\n \n // 使用 xxHash64:专为非加密场景设计,比 SHA256 快 3-5 倍\n // 直接返回 16 字符的十六进制字符串,正好匹配缩略图文件名的需求\n return await xxhash64(hashInput);\n}\n\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { OperationResult } from './types';\n\n/**\n * 剪贴板文件操作接口\n * 由于 clipboard-files 是 native 模块,由使用者在 Electron 主进程中注入\n */\nexport interface ClipboardAdapter {\n writeFiles: (paths: string[]) => void;\n readFiles: () => string[];\n}\n\n/**\n * 复制文件到剪贴板\n */\nexport async function copyFilesToClipboard(\n filePaths: string[],\n clipboard: ClipboardAdapter\n): Promise<OperationResult> {\n try {\n // 验证文件是否存在\n const cleanPaths: string[] = [];\n for (const p of filePaths) {\n try {\n await fs.access(p);\n cleanPaths.push(p);\n } catch {\n // 文件不存在,跳过\n }\n }\n\n if (cleanPaths.length === 0) {\n return { success: false, error: '没有找到有效的文件' };\n }\n\n clipboard.writeFiles(cleanPaths);\n return { success: true };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 从剪贴板读取文件路径\n */\nexport function getClipboardFiles(\n clipboard: ClipboardAdapter\n): OperationResult<{ files: string[] }> {\n try {\n const files = clipboard.readFiles();\n if (files && Array.isArray(files) && files.length > 0) {\n return { success: true, data: { files } };\n }\n return { success: false, error: '剪贴板中没有文件' };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 粘贴文件到目标目录\n */\nexport async function pasteFiles(\n targetDir: string,\n sourcePaths: string[]\n): Promise<OperationResult<{ pastedPaths: string[] }>> {\n try {\n if (!targetDir || typeof targetDir !== 'string') {\n return { success: false, error: '目标目录路径无效' };\n }\n\n if (!sourcePaths || !Array.isArray(sourcePaths) || sourcePaths.length === 0) {\n return { success: false, error: '源文件路径列表无效' };\n }\n\n const pastedPaths: string[] = [];\n\n for (const sourcePath of sourcePaths) {\n const fileName = path.basename(sourcePath);\n let destPath = path.join(targetDir, fileName);\n let counter = 1;\n\n // 自动重命名\n while (true) {\n try {\n await fs.access(destPath);\n const ext = path.extname(fileName);\n const baseName = path.basename(fileName, ext);\n destPath = path.join(targetDir, `${baseName} ${++counter}${ext}`);\n } catch {\n break;\n }\n }\n\n // 复制\n const stats = await fs.stat(sourcePath);\n if (stats.isDirectory()) {\n await copyDirectory(sourcePath, destPath);\n } else {\n await fs.copyFile(sourcePath, destPath);\n }\n\n pastedPaths.push(destPath);\n }\n\n return { success: true, data: { pastedPaths } };\n } catch (error) {\n return { success: false, error: String(error) };\n }\n}\n\n/**\n * 递归复制目录\n */\nasync function copyDirectory(source: string, dest: string): Promise<void> {\n await fs.mkdir(dest, { recursive: true });\n const entries = await fs.readdir(source, { withFileTypes: true });\n\n for (const entry of entries) {\n const sourcePath = path.join(source, entry.name);\n const destPath = path.join(dest, entry.name);\n\n if (entry.isDirectory()) {\n await copyDirectory(sourcePath, destPath);\n } else {\n await fs.copyFile(sourcePath, destPath);\n }\n }\n}\n","/**\n * 应用程序图标获取(macOS)\n * \n * 纯 Node.js 实现,使用 sips 命令转换 icns 为 PNG\n */\n\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execAsync = promisify(exec);\n\n/**\n * 从应用程序包中查找图标文件路径\n */\nasync function findAppIconPath(appPath: string): Promise<string | null> {\n const resourcesPath = path.join(appPath, 'Contents', 'Resources');\n \n try {\n // 常见的图标文件名\n const commonIconNames = [\n 'AppIcon.icns',\n 'app.icns',\n 'application.icns',\n 'icon.icns',\n ];\n \n // 先尝试读取 Info.plist 来获取图标文件名\n const infoPlistPath = path.join(appPath, 'Contents', 'Info.plist');\n try {\n const infoPlistContent = await fs.readFile(infoPlistPath, 'utf-8');\n const iconFileMatch = infoPlistContent.match(/<key>CFBundleIconFile<\\/key>\\s*<string>([^<]+)<\\/string>/);\n if (iconFileMatch && iconFileMatch[1]) {\n let iconFileName = iconFileMatch[1].trim();\n if (!iconFileName.endsWith('.icns')) {\n iconFileName += '.icns';\n }\n const iconPath = path.join(resourcesPath, iconFileName);\n try {\n await fs.access(iconPath);\n return iconPath;\n } catch {\n // 继续尝试其他方法\n }\n }\n } catch {\n // Info.plist 读取失败\n }\n \n // 尝试常见文件名\n for (const iconName of commonIconNames) {\n const iconPath = path.join(resourcesPath, iconName);\n try {\n await fs.access(iconPath);\n return iconPath;\n } catch {\n continue;\n }\n }\n \n // 列出 Resources 目录,查找 .icns 文件\n try {\n const entries = await fs.readdir(resourcesPath);\n const icnsFile = entries.find(entry => entry.toLowerCase().endsWith('.icns'));\n if (icnsFile) {\n return path.join(resourcesPath, icnsFile);\n }\n } catch {\n // 目录读取失败\n }\n \n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * 获取应用程序图标(仅 macOS)\n * \n * 使用 sips 命令将 .icns 转换为 PNG,返回 base64 data URL\n */\nexport async function getApplicationIcon(appPath: string): Promise<string | null> {\n if (process.platform !== 'darwin') {\n return null;\n }\n \n // 验证路径\n try {\n const stats = await fs.stat(appPath);\n if (!stats.isDirectory() || !appPath.endsWith('.app')) {\n return null;\n }\n } catch {\n return null;\n }\n \n // 从应用程序包中读取 .icns 文件\n const iconPath = await findAppIconPath(appPath);\n if (!iconPath) {\n return null;\n }\n \n try {\n // 使用 macOS sips 命令转换为 PNG\n const tempPngPath = path.join(os.tmpdir(), `app-icon-${Date.now()}.png`);\n await execAsync(\n `sips -s format png \"${iconPath}\" --out \"${tempPngPath}\" --resampleHeightWidthMax 128`\n );\n \n const pngBuffer = await fs.readFile(tempPngPath);\n \n // 删除临时文件\n try {\n await fs.unlink(tempPngPath);\n } catch {\n // 忽略\n }\n \n const base64 = pngBuffer.toString('base64');\n return `data:image/png;base64,${base64}`;\n } catch {\n return null;\n }\n}\n","/**\n * 文件系统监听服务\n * \n * 监听目录变化,当文件增删改时触发回调\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\n/** 文件变化类型 */\nexport type WatchEventType = 'add' | 'change' | 'remove' | 'rename';\n\n/** 文件变化事件 */\nexport interface WatchEvent {\n type: WatchEventType;\n path: string;\n filename: string;\n}\n\n/** 监听回调 */\nexport type WatchCallback = (event: WatchEvent) => void;\n\n/** 监听器实例 */\nexport interface Watcher {\n /** 停止监听 */\n close: () => void;\n /** 监听的目录路径 */\n path: string;\n}\n\n/** 防抖定时器 Map */\nconst debounceTimers = new Map<string, NodeJS.Timeout>();\n\n/** 防抖延迟(毫秒) */\nconst DEBOUNCE_DELAY = 100;\n\n/**\n * 监听目录变化\n * \n * @param dirPath 要监听的目录路径\n * @param callback 变化回调\n * @returns Watcher 实例\n */\nexport function watchDirectory(\n dirPath: string,\n callback: WatchCallback\n): Watcher {\n let watcher: fs.FSWatcher | null = null;\n \n try {\n // 使用 fs.watch 监听目录\n // 注意:fs.watch 在不同平台行为略有不同\n watcher = fs.watch(dirPath, { persistent: true }, (eventType, filename) => {\n if (!filename) return;\n \n const fullPath = path.join(dirPath, filename);\n const key = `${dirPath}:${filename}`;\n \n // 清除之前的防抖定时器\n const existingTimer = debounceTimers.get(key);\n if (existingTimer) {\n clearTimeout(existingTimer);\n }\n \n // 设置新的防抖定时器\n const timer = setTimeout(() => {\n debounceTimers.delete(key);\n \n // 判断变化类型\n fs.access(fullPath, fs.constants.F_OK, (err) => {\n let type: WatchEventType;\n \n if (err) {\n // 文件不存在,说明被删除了\n type = 'remove';\n } else if (eventType === 'rename') {\n // rename 事件可能是新增或重命名\n type = 'add';\n } else {\n // change 事件\n type = 'change';\n }\n \n callback({\n type,\n path: fullPath,\n filename,\n });\n });\n }, DEBOUNCE_DELAY);\n \n debounceTimers.set(key, timer);\n });\n \n // 错误处理\n watcher.on('error', (error) => {\n console.error('Watch error:', error);\n });\n \n } catch (error) {\n console.error('Failed to watch directory:', error);\n }\n \n return {\n close: () => {\n if (watcher) {\n watcher.close();\n watcher = null;\n }\n // 清除所有相关的防抖定时器\n for (const [key, timer] of debounceTimers.entries()) {\n if (key.startsWith(`${dirPath}:`)) {\n clearTimeout(timer);\n debounceTimers.delete(key);\n }\n }\n },\n path: dirPath,\n };\n}\n\n/**\n * 监听管理器\n * 用于管理多个监听器,确保同一目录只有一个监听器\n */\nexport class WatchManager {\n private watchers = new Map<string, { watcher: Watcher; refCount: number }>();\n private callbacks = new Map<string, Set<WatchCallback>>();\n \n /**\n * 开始监听目录\n */\n watch(dirPath: string, callback: WatchCallback): () => void {\n // 标准化路径\n const normalizedPath = path.normalize(dirPath);\n \n // 添加回调\n let callbackSet = this.callbacks.get(normalizedPath);\n if (!callbackSet) {\n callbackSet = new Set();\n this.callbacks.set(normalizedPath, callbackSet);\n }\n callbackSet.add(callback);\n \n // 检查是否已有监听器\n let watcherInfo = this.watchers.get(normalizedPath);\n \n if (watcherInfo) {\n // 增加引用计数\n watcherInfo.refCount++;\n } else {\n // 创建新监听器\n const watcher = watchDirectory(normalizedPath, (event) => {\n // 通知所有回调\n const callbacks = this.callbacks.get(normalizedPath);\n if (callbacks) {\n for (const cb of callbacks) {\n try {\n cb(event);\n } catch (error) {\n console.error('Watch callback error:', error);\n }\n }\n }\n });\n \n watcherInfo = { watcher, refCount: 1 };\n this.watchers.set(normalizedPath, watcherInfo);\n }\n \n // 返回取消监听函数\n return () => {\n this.unwatch(normalizedPath, callback);\n };\n }\n \n /**\n * 停止监听\n */\n private unwatch(dirPath: string, callback: WatchCallback): void {\n const normalizedPath = path.normalize(dirPath);\n \n // 移除回调\n const callbackSet = this.callbacks.get(normalizedPath);\n if (callbackSet) {\n callbackSet.delete(callback);\n if (callbackSet.size === 0) {\n this.callbacks.delete(normalizedPath);\n }\n }\n \n // 减少引用计数\n const watcherInfo = this.watchers.get(normalizedPath);\n if (watcherInfo) {\n watcherInfo.refCount--;\n \n // 如果没有引用了,关闭监听器\n if (watcherInfo.refCount <= 0) {\n watcherInfo.watcher.close();\n this.watchers.delete(normalizedPath);\n }\n }\n }\n \n /**\n * 关闭所有监听器\n */\n closeAll(): void {\n for (const [, watcherInfo] of this.watchers) {\n watcherInfo.watcher.close();\n }\n this.watchers.clear();\n this.callbacks.clear();\n }\n}\n\n// 全局单例\nlet globalWatchManager: WatchManager | null = null;\n\n/**\n * 获取全局监听管理器\n */\nexport function getWatchManager(): WatchManager {\n if (!globalWatchManager) {\n globalWatchManager = new WatchManager();\n }\n return globalWatchManager;\n}\n\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { getFileType } from '../utils/file-type';\nimport { getFileHash } from '../hash';\nimport { FileType } from '../types';\nimport type { \n ThumbnailDatabase, \n ImageProcessor, \n VideoProcessor,\n ThumbnailServiceOptions \n} from './types';\n\n/**\n * 检查是否为图片文件(使用统一的文件类型判断)\n */\nfunction isImageFile(filePath: string, fileType: FileType): boolean {\n return fileType === FileType.IMAGE;\n}\n\n/**\n * 检查是否为视频文件(使用统一的文件类型判断)\n */\nfunction isVideoFile(filePath: string, fileType: FileType): boolean {\n return fileType === FileType.VIDEO;\n}\n\n/**\n * 缩略图服务\n */\nexport class ThumbnailService {\n private database: ThumbnailDatabase;\n private imageProcessor: ImageProcessor | null;\n private videoProcessor: VideoProcessor | null;\n private urlEncoder: (filePath: string) => string;\n private getApplicationIcon: ((appPath: string) => Promise<string | null>) | null;\n\n constructor(options: ThumbnailServiceOptions) {\n this.database = options.database;\n this.imageProcessor = options.imageProcessor || null;\n this.videoProcessor = options.videoProcessor || null;\n this.urlEncoder = options.urlEncoder || ((p) => `file://${encodeURIComponent(p)}`);\n this.getApplicationIcon = options.getApplicationIcon || null;\n }\n\n /**\n * 获取缓存的缩略图 URL(不生成新的)\n */\n async getCachedThumbnailUrl(filePath: string): Promise<string | null> {\n try {\n const stats = await fs.stat(filePath);\n const fileType = getFileType(filePath, stats);\n \n // 如果是应用程序,获取应用程序图标(macOS)\n if (fileType === FileType.APPLICATION && this.getApplicationIcon) {\n return await this.getApplicationIcon(filePath);\n }\n \n // 只处理图片和视频\n if (fileType !== FileType.IMAGE && fileType !== FileType.VIDEO) {\n return null;\n }\n \n const mtime = stats.mtime.getTime();\n \n // 快速查询缓存(不计算哈希)\n const cachedPath = this.database.getThumbnailPathFast(filePath, mtime);\n if (cachedPath) {\n return this.urlEncoder(cachedPath);\n }\n \n // 没有缓存,异步触发生成(不阻塞)\n // 传入已获取的 stats,避免重复调用 stat()\n getFileHash(filePath, stats).then(fileHash => {\n this.generateThumbnail(filePath, fileHash, mtime).catch(() => {});\n }).catch(() => {});\n \n return null;\n } catch (error) {\n return null;\n }\n }\n\n /**\n * 获取缩略图 URL(如果没有缓存则生成)\n */\n async getThumbnailUrl(filePath: string): Promise<string | null> {\n try {\n const stats = await fs.stat(filePath);\n const fileType = getFileType(filePath, stats);\n \n // 如果是应用程序,获取应用程序图标(macOS)\n if (fileType === FileType.APPLICATION && this.getApplicationIcon) {\n return await this.getApplicationIcon(filePath);\n }\n \n // 只处理图片和视频\n if (fileType !== FileType.IMAGE && fileType !== FileType.VIDEO) {\n return null;\n }\n \n const mtime = stats.mtime.getTime();\n \n // 快速查询缓存(不计算哈希)\n const cachedPath = this.database.getThumbnailPathFast(filePath, mtime);\n if (cachedPath) {\n return this.urlEncoder(cachedPath);\n }\n \n // 缓存未命中,计算哈希并生成缩略图\n // 传入已获取的 stats,避免重复调用 stat()\n const fileHash = await getFileHash(filePath, stats);\n const thumbnailPath = await this.generateThumbnail(filePath, fileHash, mtime);\n if (thumbnailPath) {\n return this.urlEncoder(thumbnailPath);\n }\n \n return null;\n } catch (error) {\n console.error(`Error getting thumbnail for ${filePath}:`, error);\n return null;\n }\n }\n\n /**\n * 生成缩略图\n */\n async generateThumbnail(\n filePath: string,\n fileHash: string,\n mtime: number\n ): Promise<string | null> {\n // 检查缓存\n const cachedPath = this.database.getThumbnailPath(filePath, fileHash, mtime);\n if (cachedPath) {\n return cachedPath;\n }\n\n try {\n // 获取文件类型(使用统一的文件类型判断)\n const stats = await fs.stat(filePath);\n const fileType = getFileType(filePath, stats);\n \n // 生成缩略图文件名\n const hashPrefix = fileHash.substring(0, 16);\n const thumbnailFileName = `${hashPrefix}.jpg`;\n const thumbnailPath = path.join(this.database.getCacheDir(), thumbnailFileName);\n\n // 根据文件类型选择不同的生成方法(使用统一的文件类型判断)\n if (isImageFile(filePath, fileType) && this.imageProcessor) {\n await this.imageProcessor.resize(filePath, thumbnailPath, 256);\n } else if (isVideoFile(filePath, fileType) && this.videoProcessor) {\n await this.videoProcessor.screenshot(filePath, thumbnailPath, '00:00:01', '256x256');\n } else {\n return null;\n }\n\n // 保存到数据库\n this.database.saveThumbnail(filePath, fileHash, mtime, thumbnailPath);\n\n return thumbnailPath;\n } catch (error) {\n console.debug(`Error generating thumbnail for ${filePath}:`, error);\n return null;\n }\n }\n\n /**\n * 批量生成缩略图(带并发限制,避免资源耗尽)\n */\n async generateThumbnailsBatch(\n files: Array<{ path: string; hash: string; mtime: number }>,\n concurrency: number = 3 // 默认并发数为 3,避免同时处理太多文件\n ): Promise<void> {\n // 并发控制:使用 Promise 池\n const execute = async (file: { path: string; hash: string; mtime: number }) => {\n try {\n await this.generateThumbnail(file.path, file.hash, file.mtime);\n } catch (error) {\n // 单个文件失败不影响其他文件\n console.debug(`Failed to generate thumbnail for ${file.path}:`, error);\n }\n };\n\n // 分批处理\n for (let i = 0; i < files.length; i += concurrency) {\n const batch = files.slice(i, i + concurrency);\n await Promise.allSettled(batch.map(execute));\n }\n }\n\n /**\n * 删除缩略图\n */\n deleteThumbnail(filePath: string): void {\n this.database.deleteThumbnail(filePath);\n }\n\n /**\n * 清理旧缩略图\n */\n cleanupOldThumbnails(): void {\n this.database.cleanupOldThumbnails();\n }\n}\n\n// 全局单例\nlet thumbnailService: ThumbnailService | null = null;\n\n/**\n * 初始化缩略图服务\n */\nexport function initThumbnailService(options: ThumbnailServiceOptions): ThumbnailService {\n thumbnailService = new ThumbnailService(options);\n return thumbnailService;\n}\n\n/**\n * 获取缩略图服务实例\n */\nexport function getThumbnailService(): ThumbnailService | null {\n return thumbnailService;\n}\n","/**\n * SQLite 缩略图数据库实现\n * \n * 需要安装 better-sqlite3:npm install better-sqlite3\n */\n\nimport Database from 'better-sqlite3';\nimport path from 'node:path';\nimport { existsSync, mkdirSync } from 'node:fs';\nimport type { ThumbnailDatabase } from './types';\n\nexport interface SqliteThumbnailDatabaseOptions {\n /**\n * 缩略图存储目录(绝对路径)。如果提供,则优先使用该目录。\n * 适用于将本库嵌入到其他应用时,自定义缩略图缓存位置。\n */\n thumbnailDir?: string;\n\n /**\n * 缩略图子目录名(相对 userDataPath)。默认:`thumbnails`\n * 仅在未提供 thumbnailDir 时生效。\n */\n dirName?: string;\n\n /**\n * 缩略图数据库文件名(默认:`thumbnails.db`)\n * 仅在未提供 dbPath 时生效。\n */\n dbFileName?: string;\n\n /**\n * 缩略图数据库文件路径(绝对路径)。如果提供,则优先使用该路径。\n * 注意:缩略图 jpg 仍然会写入 thumbnailDir(若未显式提供,则使用 dbPath 所在目录)。\n */\n dbPath?: string;\n}\n\n/**\n * SQLite 缩略图数据库实现\n */\nexport class SqliteThumbnailDatabase implements ThumbnailDatabase {\n private db: Database.Database | null = null;\n private cacheDir: string;\n private dbPath: string;\n\n constructor(userDataPath: string, options: SqliteThumbnailDatabaseOptions = {}) {\n /**\n * 注意:不要使用 userData/cache\n * 在 macOS 上文件系统通常大小写不敏感,`cache` 会和 Electron/Chromium 的 `Cache` 目录冲突,\n * 导致缩略图数据库/文件被 Chromium 缓存机制清理,出现“重启后又重新生成”的问题。\n */\n const defaultDirName = options.dirName || 'thumbnails';\n const defaultDbFileName = options.dbFileName || 'thumbnails.db';\n\n // 如果传入了 dbPath,但没传 thumbnailDir,则缩略图目录默认跟随 dbPath 所在目录\n const inferredDirFromDbPath = options.dbPath ? path.dirname(options.dbPath) : null;\n\n this.cacheDir = options.thumbnailDir\n ? options.thumbnailDir\n : (inferredDirFromDbPath || path.join(userDataPath, defaultDirName));\n\n this.dbPath = options.dbPath\n ? options.dbPath\n : path.join(this.cacheDir, defaultDbFileName);\n \n if (!existsSync(this.cacheDir)) {\n mkdirSync(this.cacheDir, { recursive: true });\n }\n }\n\n /**\n * 初始化数据库\n */\n init(): void {\n if (this.db) return;\n\n // 使用 WAL 模式提高并发性能,避免锁定问题\n this.db = new Database(this.dbPath, { \n fileMustExist: false,\n });\n \n // 启用 WAL 模式\n this.db.pragma('journal_mode = WAL');\n\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS thumbnails (\n file_path TEXT PRIMARY KEY,\n file_hash TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n thumbnail_path TEXT NOT NULL,\n created_at INTEGER NOT NULL\n );\n \n CREATE INDEX IF NOT EXISTS idx_file_hash ON thumbnails(file_hash);\n CREATE INDEX IF NOT EXISTS idx_mtime ON thumbnails(mtime);\n `);\n }\n\n getCacheDir(): string {\n return this.cacheDir;\n }\n\n /**\n * 快速查询缩略图(用路径和修改时间,不需要哈希)\n * 比计算文件哈希快很多\n */\n getThumbnailPathFast(filePath: string, mtime: number): string | null {\n if (!this.db) this.init();\n\n const stmt = this.db!.prepare(`\n SELECT thumbnail_path \n FROM thumbnails \n WHERE file_path = ? AND mtime = ?\n `);\n\n const row = stmt.get(filePath, mtime) as { thumbnail_path: string } | undefined;\n\n if (row && existsSync(row.thumbnail_path)) {\n return row.thumbnail_path;\n }\n\n return null;\n }\n\n getThumbnailPath(filePath: string, fileHash: string, mtime: number): string | null {\n if (!this.db) this.init();\n\n const stmt = this.db!.prepare(`\n SELECT thumbnail_path, mtime \n FROM thumbnails \n WHERE file_path = ? AND file_hash = ?\n `);\n\n const row = stmt.get(filePath, fileHash) as { thumbnail_path: string; mtime: number } | undefined;\n\n if (row && row.mtime === mtime && existsSync(row.thumbnail_path)) {\n return row.thumbnail_path;\n }\n\n return null;\n }\n\n saveThumbnail(filePath: string, fileHash: string, mtime: number, thumbnailPath: string): void {\n if (!this.db) this.init();\n\n const stmt = this.db!.prepare(`\n INSERT OR REPLACE INTO thumbnails \n (file_path, file_hash, mtime, thumbnail_path, created_at)\n VALUES (?, ?, ?, ?, ?)\n `);\n\n stmt.run(filePath, fileHash, mtime, thumbnailPath, Date.now());\n }\n\n deleteThumbnail(filePath: string): void {\n if (!this.db) this.init();\n\n const stmt = this.db!.prepare('DELETE FROM thumbnails WHERE file_path = ?');\n stmt.run(filePath);\n }\n\n cleanupOldThumbnails(): void {\n if (!this.db) this.init();\n\n const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;\n const stmt = this.db!.prepare('DELETE FROM thumbnails WHERE created_at < ?');\n stmt.run(thirtyDaysAgo);\n }\n\n close(): void {\n if (this.db) {\n // 在关闭前执行 checkpoint,确保 WAL 文件中的数据写入主数据库\n try {\n this.db.pragma('wal_checkpoint(TRUNCATE)');\n } catch (error) {\n // 忽略 checkpoint 错误\n }\n this.db.close();\n this.db = null;\n }\n }\n}\n\n// 单例\nlet thumbnailDb: SqliteThumbnailDatabase | null = null;\n\n/**\n * 创建 SQLite 缩略图数据库\n */\n/**\n * 创建 SQLite 缩略图数据库(统一入口:仅支持 options 对象)\n * 说明:不再支持“传 dbPath 当作参数”的用法;宿主需要显式传入 userDataPath(以及可选的目录/文件覆盖)。\n */\nexport function createSqliteThumbnailDatabase(options: { userDataPath: string } & SqliteThumbnailDatabaseOptions): SqliteThumbnailDatabase {\n if (!thumbnailDb) {\n thumbnailDb = new SqliteThumbnailDatabase(options.userDataPath, options);\n thumbnailDb.init();\n }\n return thumbnailDb;\n}\n\n/**\n * 获取缩略图数据库实例\n */\nexport function getSqliteThumbnailDatabase(): SqliteThumbnailDatabase | null {\n return thumbnailDb;\n}\n\n/**\n * 关闭缩略图数据库(应在应用退出时调用)\n */\nexport function closeThumbnailDatabase(): void {\n if (thumbnailDb) {\n thumbnailDb.close();\n thumbnailDb = null;\n }\n}\n","/**\n * 缩略图处理器工厂函数\n * \n * 需要安装:\n * - sharp: npm install sharp\n * - ffmpeg-static: npm install ffmpeg-static\n */\n\nimport { spawn } from 'node:child_process';\nimport type { ImageProcessor, VideoProcessor } from './types';\n\n/**\n * Sharp 类型定义(宽松类型,兼容 sharp 模块)\n */\ninterface SharpInstance {\n resize(options?: { width?: number; height?: number; withoutEnlargement?: boolean }): SharpInstance;\n jpeg(options?: { quality?: number; optimiseCoding?: boolean }): SharpInstance;\n toFile(outputPath: string): Promise<unknown>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype SharpModule = (input: string, options?: any) => SharpInstance;\n\n/**\n * 创建 Sharp 图片处理器\n * \n * @example\n * ```ts\n * import sharp from 'sharp';\n * import { createSharpImageProcessor } from '@huyooo/file-explorer-core';\n * \n * const imageProcessor = createSharpImageProcessor(sharp);\n * ```\n */\nexport function createSharpImageProcessor(sharp: SharpModule): ImageProcessor {\n return {\n async resize(filePath: string, outputPath: string, size: number) {\n // 直接缩放,不处理 EXIF 旋转(大多数现代设备已自动旋转像素数据)\n // 如果发现缩略图方向错误,可以添加 .rotate() 自动处理 EXIF 方向\n // 只设置宽度,高度自适应,保持原图比例\n // 这样缩略图可以作为加载原图时的占位符,平滑过渡\n // 显示时的裁剪/适配可以在前端 CSS 中处理\n await sharp(filePath)\n .resize({ \n width: size, \n withoutEnlargement: true,\n })\n .jpeg({ \n quality: 80,\n optimiseCoding: true, // 优化编码,提升压缩率\n })\n .toFile(outputPath);\n }\n };\n}\n\n/**\n * 创建 FFmpeg 视频处理器(使用 child_process 直接调用 ffmpeg)\n * \n * @param ffmpegPath - ffmpeg 可执行文件路径(来自 ffmpeg-static)\n * \n * @example\n * ```ts\n * import ffmpegStatic from 'ffmpeg-static';\n * import { createFfmpegVideoProcessor } from '@huyooo/file-explorer-core';\n * \n * const videoProcessor = ffmpegStatic \n * ? createFfmpegVideoProcessor(ffmpegStatic) \n * : undefined;\n * ```\n */\nexport function createFfmpegVideoProcessor(ffmpegPath: string): VideoProcessor {\n return {\n async screenshot(filePath: string, outputPath: string, timestamp: string, size: string): Promise<void> {\n // 解析尺寸 \"256x256\" -> 只使用宽度 \"256\"\n // 保持原图比例,高度自适应(与图片缩略图保持一致)\n const width = size.split('x')[0];\n \n // 使用 spawn 而不是 execFile,以便过滤 stderr 中的错误和添加超时控制\n // 借鉴 pixflow 的优点:使用 thumbnail 滤镜和 mjpeg 编码器,性能更好\n return new Promise((resolve, reject) => {\n const ffmpeg = spawn(ffmpegPath, [\n '-y',\n '-ss', timestamp,\n '-i', filePath,\n '-vframes', '1',\n '-vf', `thumbnail,scale=${width}:-1:force_original_aspect_ratio=decrease`,\n // 使用 mjpeg 编码器,性能更好(借鉴 pixflow)\n '-c:v', 'mjpeg',\n '-q:v', '6', // 质量参数,6 表示中等质量(范围 2-31,数值越小质量越高)\n outputPath\n ]);\n \n // 设置超时(30秒)\n const timeout = setTimeout(() => {\n ffmpeg.kill();\n reject(new Error('Video thumbnail generation timeout'));\n }, 30000);\n \n // 过滤 stderr 中的像素格式错误(这些错误不影响功能)\n ffmpeg.stderr.on('data', (data: Buffer) => {\n const output = data.toString();\n // 过滤掉像素格式相关的错误信息\n if (output.includes('Unsupported pixel format')) {\n // 静默忽略,这些错误不影响缩略图生成\n return;\n }\n });\n \n ffmpeg.on('close', (code) => {\n clearTimeout(timeout);\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`ffmpeg exited with code ${code}`));\n }\n });\n \n ffmpeg.on('error', (error) => {\n clearTimeout(timeout);\n reject(error);\n });\n });\n }\n };\n}\n","/**\n * 媒体格式检测\n * \n * 判断媒体文件是否需要转码,以及使用何种转码方式\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport path from 'node:path';\nimport type { MediaType, MediaFormatInfo, TranscodeInfo, TranscodeMethod } from './types';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * 浏览器原生支持的视频格式\n */\nconst BROWSER_VIDEO_CONTAINERS = new Set(['mp4', 'webm', 'ogg', 'ogv']);\nconst BROWSER_VIDEO_CODECS = new Set(['h264', 'avc1', 'vp8', 'vp9', 'theora', 'av1']);\nconst BROWSER_AUDIO_CODECS = new Set(['aac', 'mp3', 'opus', 'vorbis', 'flac']);\n\n/**\n * 浏览器原生支持的音频格式\n */\nconst BROWSER_AUDIO_CONTAINERS = new Set(['mp3', 'wav', 'ogg', 'oga', 'webm', 'm4a', 'aac', 'flac']);\nconst BROWSER_AUDIO_ONLY_CODECS = new Set(['mp3', 'aac', 'opus', 'vorbis', 'flac', 'pcm_s16le', 'pcm_s24le']);\n\n/**\n * 可以快速转封装到 MP4 的视频编码\n */\nconst REMUXABLE_VIDEO_CODECS = new Set(['h264', 'avc1', 'hevc', 'h265']);\n\n/**\n * 可以快速转封装到 M4A 的音频编码\n */\nconst REMUXABLE_AUDIO_CODECS = new Set(['aac', 'alac']);\n\n/**\n * 视频扩展名映射\n */\nconst VIDEO_EXTENSIONS = new Set([\n 'mp4', 'mkv', 'avi', 'mov', 'wmv', 'flv', 'webm', 'ogv', 'ogg',\n 'm4v', 'mpeg', 'mpg', '3gp', 'ts', 'mts', 'm2ts', 'vob', 'rmvb', 'rm'\n]);\n\n/**\n * 音频扩展名映射\n */\nconst AUDIO_EXTENSIONS = new Set([\n 'mp3', 'wav', 'flac', 'aac', 'm4a', 'ogg', 'oga', 'wma', 'ape',\n 'alac', 'aiff', 'aif', 'opus', 'mid', 'midi', 'wv', 'mka'\n]);\n\n/**\n * 从文件扩展名判断媒体类型\n */\nexport function getMediaTypeByExtension(filePath: string): MediaType | null {\n const ext = path.extname(filePath).toLowerCase().slice(1);\n if (VIDEO_EXTENSIONS.has(ext)) return 'video';\n if (AUDIO_EXTENSIONS.has(ext)) return 'audio';\n return null;\n}\n\n/**\n * 使用 ffprobe 获取媒体格式信息\n */\nexport async function getMediaFormat(\n filePath: string, \n ffprobePath: string\n): Promise<MediaFormatInfo | null> {\n try {\n const { stdout } = await execFileAsync(ffprobePath, [\n '-v', 'quiet',\n '-print_format', 'json',\n '-show_format',\n '-show_streams',\n filePath\n ]);\n\n const data = JSON.parse(stdout);\n const format = data.format || {};\n const streams = data.streams || [];\n\n // 查找视频流和音频流\n const videoStream = streams.find((s: { codec_type: string }) => s.codec_type === 'video');\n const audioStream = streams.find((s: { codec_type: string }) => s.codec_type === 'audio');\n\n // 判断媒体类型\n const type: MediaType = videoStream ? 'video' : 'audio';\n\n // 获取容器格式\n const formatName = format.format_name || '';\n const container = formatName.split(',')[0].toLowerCase();\n\n return {\n type,\n container,\n videoCodec: videoStream?.codec_name?.toLowerCase(),\n audioCodec: audioStream?.codec_name?.toLowerCase(),\n duration: parseFloat(format.duration) || undefined,\n width: videoStream?.width,\n height: videoStream?.height,\n bitrate: parseInt(format.bit_rate) || undefined,\n };\n } catch {\n // ffprobe 失败,回退到扩展名判断\n const type = getMediaTypeByExtension(filePath);\n if (!type) return null;\n \n const ext = path.extname(filePath).toLowerCase().slice(1);\n return {\n type,\n container: ext,\n };\n }\n}\n\n/**\n * 判断视频是否可以直接播放\n */\nfunction canPlayVideoDirectly(format: MediaFormatInfo): boolean {\n // 检查容器格式\n if (!BROWSER_VIDEO_CONTAINERS.has(format.container)) {\n return false;\n }\n\n // 检查视频编码\n if (format.videoCodec && !BROWSER_VIDEO_CODECS.has(format.videoCodec)) {\n return false;\n }\n\n // 检查音频编码(如果有)\n if (format.audioCodec && !BROWSER_AUDIO_CODECS.has(format.audioCodec)) {\n return false;\n }\n\n return true;\n}\n\n/**\n * 判断音频是否可以直接播放\n */\nfunction canPlayAudioDirectly(format: MediaFormatInfo): boolean {\n // 检查容器格式\n if (!BROWSER_AUDIO_CONTAINERS.has(format.container)) {\n return false;\n }\n\n // 检查音频编码\n if (format.audioCodec && !BROWSER_AUDIO_ONLY_CODECS.has(format.audioCodec)) {\n return false;\n }\n\n return true;\n}\n\n/**\n * 判断视频是否可以转封装(不重新编码)\n */\nfunction canRemuxVideo(format: MediaFormatInfo): boolean {\n // 视频编码必须是可以直接封装到 MP4 的\n if (!format.videoCodec || !REMUXABLE_VIDEO_CODECS.has(format.videoCodec)) {\n return false;\n }\n\n // 音频编码必须是浏览器支持的,或者可以转封装的\n if (format.audioCodec) {\n const audioOk = BROWSER_AUDIO_CODECS.has(format.audioCodec) || \n REMUXABLE_AUDIO_CODECS.has(format.audioCodec);\n if (!audioOk) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * 判断音频是否可以转封装\n */\nfunction canRemuxAudio(format: MediaFormatInfo): boolean {\n return format.audioCodec ? REMUXABLE_AUDIO_CODECS.has(format.audioCodec) : false;\n}\n\n/**\n * 估算转码时间(秒)\n * 基于时长和转码方式进行粗略估算\n */\nfunction estimateTranscodeTime(\n duration: number | undefined, \n method: TranscodeMethod\n): number | undefined {\n if (!duration || method === 'direct') return undefined;\n \n // 转封装非常快,约为原时长的 1/50\n if (method === 'remux') {\n return Math.ceil(duration / 50);\n }\n \n // 转码较慢,约为原时长的 1/3 到 1/2(取决于硬件)\n return Math.ceil(duration / 3);\n}\n\n/**\n * 检测媒体文件的转码需求\n */\nexport async function detectTranscodeNeeds(\n filePath: string,\n ffprobePath: string\n): Promise<TranscodeInfo> {\n // 获取格式信息\n const formatInfo = await getMediaFormat(filePath, ffprobePath);\n \n if (!formatInfo) {\n // 无法识别格式,尝试直接播放\n const type = getMediaTypeByExtension(filePath) || 'video';\n return {\n type,\n needsTranscode: false,\n method: 'direct',\n };\n }\n\n const { type } = formatInfo;\n\n // 检查是否可以直接播放\n if (type === 'video') {\n if (canPlayVideoDirectly(formatInfo)) {\n return {\n type,\n needsTranscode: false,\n method: 'direct',\n formatInfo,\n };\n }\n\n // 检查是否可以转封装\n if (canRemuxVideo(formatInfo)) {\n return {\n type,\n needsTranscode: true,\n method: 'remux',\n formatInfo,\n targetFormat: 'mp4',\n estimatedTime: estimateTranscodeTime(formatInfo.duration, 'remux'),\n };\n }\n\n // 需要完整转码\n return {\n type,\n needsTranscode: true,\n method: 'transcode',\n formatInfo,\n targetFormat: 'mp4',\n estimatedTime: estimateTranscodeTime(formatInfo.duration, 'transcode'),\n };\n }\n\n // 音频处理\n if (canPlayAudioDirectly(formatInfo)) {\n return {\n type,\n needsTranscode: false,\n method: 'direct',\n formatInfo,\n };\n }\n\n // 检查是否可以转封装\n if (canRemuxAudio(formatInfo)) {\n return {\n type,\n needsTranscode: true,\n method: 'remux',\n formatInfo,\n targetFormat: 'm4a',\n estimatedTime: estimateTranscodeTime(formatInfo.duration, 'remux'),\n };\n }\n\n // 需要完整转码\n return {\n type,\n needsTranscode: true,\n method: 'transcode',\n formatInfo,\n targetFormat: 'mp3',\n estimatedTime: estimateTranscodeTime(formatInfo.duration, 'transcode'),\n };\n}\n\n","/**\n * 媒体转码器\n * \n * 使用 ffmpeg 进行媒体格式转换\n */\n\nimport { spawn } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport os from 'node:os';\nimport type { \n TranscodeInfo, \n TranscodeResult, \n TranscodeProgress,\n ProgressCallback \n} from './types';\n\n/**\n * 生成临时输出文件路径\n */\nasync function getTempOutputPath(\n sourceFile: string, \n targetFormat: string,\n tempDir?: string\n): Promise<string> {\n const dir = tempDir || path.join(os.tmpdir(), 'file-explorer-media');\n \n // 确保目录存在\n await fs.mkdir(dir, { recursive: true });\n \n const baseName = path.basename(sourceFile, path.extname(sourceFile));\n const timestamp = Date.now();\n const outputName = `${baseName}_${timestamp}.${targetFormat}`;\n \n return path.join(dir, outputName);\n}\n\n/**\n * 解析 ffmpeg 输出中的进度信息\n */\nfunction parseProgress(\n stderr: string, \n duration: number | undefined\n): TranscodeProgress | null {\n // 匹配时间: time=00:01:23.45\n const timeMatch = stderr.match(/time=(\\d+):(\\d+):(\\d+)\\.(\\d+)/);\n if (!timeMatch) return null;\n\n const hours = parseInt(timeMatch[1]);\n const minutes = parseInt(timeMatch[2]);\n const seconds = parseInt(timeMatch[3]);\n const ms = parseInt(timeMatch[4]);\n\n const currentTime = hours * 3600 + minutes * 60 + seconds + ms / 100;\n\n // 匹配速度: speed=2.5x\n const speedMatch = stderr.match(/speed=\\s*([\\d.]+)x/);\n const speed = speedMatch ? `${speedMatch[1]}x` : undefined;\n\n // 计算进度百分比\n let percent = 0;\n if (duration && duration > 0) {\n percent = Math.min(100, Math.round((currentTime / duration) * 100));\n }\n\n return {\n percent,\n time: currentTime,\n duration,\n speed,\n };\n}\n\n/**\n * 执行视频转封装(快速,不重新编码)\n */\nasync function remuxVideo(\n ffmpegPath: string,\n inputPath: string,\n outputPath: string,\n duration: number | undefined,\n onProgress?: ProgressCallback\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const args = [\n '-i', inputPath,\n '-c', 'copy', // 复制流,不重新编码\n '-movflags', '+faststart', // 优化 MP4 播放\n '-y', // 覆盖输出文件\n outputPath\n ];\n\n const ffmpeg = spawn(ffmpegPath, args);\n let stderrBuffer = '';\n\n ffmpeg.stderr.on('data', (data: Buffer) => {\n stderrBuffer += data.toString();\n \n if (onProgress) {\n const progress = parseProgress(stderrBuffer, duration);\n if (progress) {\n onProgress(progress);\n }\n }\n });\n\n ffmpeg.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`ffmpeg exited with code ${code}`));\n }\n });\n\n ffmpeg.on('error', reject);\n });\n}\n\n/**\n * 执行视频转码\n */\nasync function transcodeVideo(\n ffmpegPath: string,\n inputPath: string,\n outputPath: string,\n duration: number | undefined,\n onProgress?: ProgressCallback\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const args = [\n '-i', inputPath,\n '-c:v', 'libx264', // H.264 编码\n '-preset', 'fast', // 编码速度预设\n '-crf', '23', // 质量(18-28,越小越好)\n '-c:a', 'aac', // AAC 音频\n '-b:a', '192k', // 音频比特率\n '-movflags', '+faststart',\n '-y',\n outputPath\n ];\n\n const ffmpeg = spawn(ffmpegPath, args);\n let stderrBuffer = '';\n\n ffmpeg.stderr.on('data', (data: Buffer) => {\n stderrBuffer += data.toString();\n \n if (onProgress) {\n const progress = parseProgress(stderrBuffer, duration);\n if (progress) {\n onProgress(progress);\n }\n }\n });\n\n ffmpeg.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`ffmpeg exited with code ${code}`));\n }\n });\n\n ffmpeg.on('error', reject);\n });\n}\n\n/**\n * 执行音频转封装\n */\nasync function remuxAudio(\n ffmpegPath: string,\n inputPath: string,\n outputPath: string,\n duration: number | undefined,\n onProgress?: ProgressCallback\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const args = [\n '-i', inputPath,\n '-c', 'copy',\n '-y',\n outputPath\n ];\n\n const ffmpeg = spawn(ffmpegPath, args);\n let stderrBuffer = '';\n\n ffmpeg.stderr.on('data', (data: Buffer) => {\n stderrBuffer += data.toString();\n \n if (onProgress) {\n const progress = parseProgress(stderrBuffer, duration);\n if (progress) {\n onProgress(progress);\n }\n }\n });\n\n ffmpeg.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`ffmpeg exited with code ${code}`));\n }\n });\n\n ffmpeg.on('error', reject);\n });\n}\n\n/**\n * 执行音频转码\n */\nasync function transcodeAudio(\n ffmpegPath: string,\n inputPath: string,\n outputPath: string,\n duration: number | undefined,\n onProgress?: ProgressCallback\n): Promise<void> {\n return new Promise((resolve, reject) => {\n // 根据输出格式选择编码器\n const ext = path.extname(outputPath).toLowerCase();\n const isM4a = ext === '.m4a';\n\n const args = [\n '-i', inputPath,\n '-c:a', isM4a ? 'aac' : 'libmp3lame',\n '-b:a', '192k',\n '-y',\n outputPath\n ];\n\n const ffmpeg = spawn(ffmpegPath, args);\n let stderrBuffer = '';\n\n ffmpeg.stderr.on('data', (data: Buffer) => {\n stderrBuffer += data.toString();\n \n if (onProgress) {\n const progress = parseProgress(stderrBuffer, duration);\n if (progress) {\n onProgress(progress);\n }\n }\n });\n\n ffmpeg.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`ffmpeg exited with code ${code}`));\n }\n });\n\n ffmpeg.on('error', reject);\n });\n}\n\n/**\n * 执行媒体转码\n */\nexport async function transcodeMedia(\n ffmpegPath: string,\n inputPath: string,\n transcodeInfo: TranscodeInfo,\n tempDir?: string,\n onProgress?: ProgressCallback\n): Promise<TranscodeResult> {\n try {\n // 如果不需要转码,直接返回原文件\n if (!transcodeInfo.needsTranscode) {\n return {\n success: true,\n outputPath: inputPath,\n };\n }\n\n // 生成输出路径\n const targetFormat = transcodeInfo.targetFormat || \n (transcodeInfo.type === 'video' ? 'mp4' : 'mp3');\n const outputPath = await getTempOutputPath(inputPath, targetFormat, tempDir);\n\n // 获取时长用于进度计算\n const duration = transcodeInfo.formatInfo?.duration;\n\n // 根据媒体类型和转码方式执行转换\n if (transcodeInfo.type === 'video') {\n if (transcodeInfo.method === 'remux') {\n await remuxVideo(ffmpegPath, inputPath, outputPath, duration, onProgress);\n } else {\n await transcodeVideo(ffmpegPath, inputPath, outputPath, duration, onProgress);\n }\n } else {\n if (transcodeInfo.method === 'remux') {\n await remuxAudio(ffmpegPath, inputPath, outputPath, duration, onProgress);\n } else {\n await transcodeAudio(ffmpegPath, inputPath, outputPath, duration, onProgress);\n }\n }\n\n // 完成时发送 100% 进度\n if (onProgress) {\n onProgress({ percent: 100, duration });\n }\n\n return {\n success: true,\n outputPath,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n}\n\n/**\n * 清理临时转码文件\n */\nexport async function cleanupTranscodedFile(filePath: string): Promise<void> {\n try {\n // 只删除临时目录中的文件\n if (filePath.includes('file-explorer-media')) {\n await fs.unlink(filePath);\n }\n } catch {\n // 忽略删除失败\n }\n}\n\n/**\n * 清理所有临时转码文件\n */\nexport async function cleanupAllTranscodedFiles(tempDir?: string): Promise<void> {\n const dir = tempDir || path.join(os.tmpdir(), 'file-explorer-media');\n \n try {\n const files = await fs.readdir(dir);\n await Promise.all(\n files.map(file => fs.unlink(path.join(dir, file)).catch(() => {}))\n );\n } catch {\n // 目录可能不存在\n }\n}\n\n","/**\n * 统一媒体服务\n * \n * 提供媒体格式检测、转码、元数据获取等功能\n */\n\nimport path from 'node:path';\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { detectTranscodeNeeds, getMediaFormat } from './format-detector';\nimport { transcodeMedia, cleanupTranscodedFile, cleanupAllTranscodedFiles } from './transcoder';\nimport type { \n MediaServiceOptions, \n TranscodeInfo, \n TranscodeResult, \n MediaMetadata,\n ProgressCallback \n} from './types';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * 媒体服务实例\n */\nlet mediaServiceInstance: MediaService | null = null;\n\n/**\n * 媒体服务类\n */\nexport class MediaService {\n private ffmpegPath: string;\n private ffprobePath: string;\n private tempDir?: string;\n private urlEncoder?: (filePath: string) => string;\n\n // 缓存转码信息,避免重复检测\n private transcodeInfoCache = new Map<string, TranscodeInfo>();\n // 缓存已转码文件路径\n private transcodedFiles = new Map<string, string>();\n\n constructor(options: MediaServiceOptions) {\n this.ffmpegPath = options.ffmpegPath;\n // ffprobe 通常与 ffmpeg 在同一目录\n this.ffprobePath = options.ffprobePath || \n path.join(path.dirname(options.ffmpegPath), 'ffprobe');\n this.tempDir = options.tempDir;\n this.urlEncoder = options.urlEncoder;\n }\n\n /**\n * 检测文件是否需要转码\n */\n async needsTranscode(filePath: string): Promise<TranscodeInfo> {\n // 检查缓存\n const cached = this.transcodeInfoCache.get(filePath);\n if (cached) {\n return cached;\n }\n\n const info = await detectTranscodeNeeds(filePath, this.ffprobePath);\n this.transcodeInfoCache.set(filePath, info);\n return info;\n }\n\n /**\n * 执行转码并返回可播放的 URL\n */\n async transcode(\n filePath: string,\n onProgress?: ProgressCallback\n ): Promise<TranscodeResult> {\n // 检查是否已经转码过\n const existingOutput = this.transcodedFiles.get(filePath);\n if (existingOutput) {\n return {\n success: true,\n outputPath: existingOutput,\n url: this.urlEncoder ? this.urlEncoder(existingOutput) : `file://${existingOutput}`,\n };\n }\n\n // 获取转码信息\n const transcodeInfo = await this.needsTranscode(filePath);\n\n // 如果不需要转码,直接返回原文件\n if (!transcodeInfo.needsTranscode) {\n const url = this.urlEncoder ? this.urlEncoder(filePath) : `file://${filePath}`;\n return {\n success: true,\n outputPath: filePath,\n url,\n };\n }\n\n // 执行转码\n const result = await transcodeMedia(\n this.ffmpegPath,\n filePath,\n transcodeInfo,\n this.tempDir,\n onProgress\n );\n\n if (result.success && result.outputPath) {\n // 缓存转码结果\n this.transcodedFiles.set(filePath, result.outputPath);\n // 生成 URL\n result.url = this.urlEncoder \n ? this.urlEncoder(result.outputPath) \n : `file://${result.outputPath}`;\n }\n\n return result;\n }\n\n /**\n * 获取媒体元数据\n */\n async getMetadata(filePath: string): Promise<MediaMetadata | null> {\n try {\n const { stdout } = await execFileAsync(this.ffprobePath, [\n '-v', 'quiet',\n '-print_format', 'json',\n '-show_format',\n '-show_streams',\n filePath\n ]);\n\n const data = JSON.parse(stdout);\n const format = data.format || {};\n const tags = format.tags || {};\n\n const formatInfo = await getMediaFormat(filePath, this.ffprobePath);\n if (!formatInfo) return null;\n\n return {\n filePath,\n type: formatInfo.type,\n duration: parseFloat(format.duration) || 0,\n format: formatInfo,\n title: tags.title || tags.TITLE,\n artist: tags.artist || tags.ARTIST,\n album: tags.album || tags.ALBUM,\n year: tags.date || tags.DATE || tags.year || tags.YEAR,\n };\n } catch {\n return null;\n }\n }\n\n /**\n * 获取可播放的 URL\n * 如果文件需要转码,则执行转码;否则直接返回文件 URL\n */\n async getPlayableUrl(\n filePath: string,\n onProgress?: ProgressCallback\n ): Promise<string | null> {\n const result = await this.transcode(filePath, onProgress);\n return result.success ? (result.url || null) : null;\n }\n\n /**\n * 清理指定文件的转码缓存\n */\n async cleanupFile(filePath: string): Promise<void> {\n const transcodedPath = this.transcodedFiles.get(filePath);\n if (transcodedPath) {\n await cleanupTranscodedFile(transcodedPath);\n this.transcodedFiles.delete(filePath);\n }\n this.transcodeInfoCache.delete(filePath);\n }\n\n /**\n * 清理所有转码缓存\n */\n async cleanup(): Promise<void> {\n await cleanupAllTranscodedFiles(this.tempDir);\n this.transcodedFiles.clear();\n this.transcodeInfoCache.clear();\n }\n\n /**\n * 清除缓存(不删除文件)\n */\n clearCache(): void {\n this.transcodeInfoCache.clear();\n }\n}\n\n/**\n * 初始化媒体服务\n */\nexport function initMediaService(options: MediaServiceOptions): MediaService {\n mediaServiceInstance = new MediaService(options);\n return mediaServiceInstance;\n}\n\n/**\n * 获取媒体服务实例\n */\nexport function getMediaService(): MediaService | null {\n return mediaServiceInstance;\n}\n\n/**\n * 创建媒体服务(不设为全局实例)\n */\nexport function createMediaService(options: MediaServiceOptions): MediaService {\n return new MediaService(options);\n}\n\n"],"mappings":"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","names":["FileType","path","path","path","fs","path","dirname","basename","fs","fs","fs","path","fs","path","path","platform","path","fs","path","getPlatform","path","fs","fs","path","copyDirectory","fs","path","os","exec","promisify","execAsync","fs","path","fs","path","fs","path","path","promisify","path","VIDEO_EXTENSIONS","type","spawn","fs","path","os","path","execFile","promisify","execFileAsync","promisify","execFile","path"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huyooo/file-explorer-core",
|
|
3
|
-
"version": "0.3
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "File Explorer Core - File system operations for Node.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -24,13 +24,14 @@
|
|
|
24
24
|
"clean": "rm -rf dist"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"compressing": "^2.0.0",
|
|
27
28
|
"fdir": "^6.5.0",
|
|
28
29
|
"hash-wasm": "^4.12.0"
|
|
29
30
|
},
|
|
30
31
|
"peerDependencies": {
|
|
31
32
|
"better-sqlite3": ">=9.0.0",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
33
|
+
"ffmpeg-static": ">=5.0.0",
|
|
34
|
+
"sharp": ">=0.33.0"
|
|
34
35
|
},
|
|
35
36
|
"peerDependenciesMeta": {
|
|
36
37
|
"better-sqlite3": {
|