Back to Cherry Studio

File Module IPC Redesign

v2-refactor-temp/docs/file-manager/ipc-redesign.md

2.0.037.3 KB
Original Source

File Module IPC Redesign

⚠️ OUTDATED / SUPERSEDED(2026-04-21)

本文档捕获的是早期设计,关键术语/决策已被后续评审推翻:

  • FileManager.createEntry({origin}) 已拆分为 createInternalEntry + ensureExternalEntry(A-7)
  • External entry 不再进入 trash 生命周期(fe_external_no_delete CHECK)
  • externalPath 唯一性由 partial unique 升级为 global unique
  • permanentDelete 对 external 只删 DB 行,不触碰物理文件

实现准绳请以以下文档为准

本文档保留用于追溯设计演进,不要据此指导实现。


v1 有 52 个文件相关 IPC(44 File + 2 Fs + 1 Open_Path + 5 App 路径工具),v2 由 FileManager 统一管理。

架构

设计动机

Renderer 需要统一的文件操作入口(一个 read 既能读 entry 也能读外部路径),但 main process 内部 entry 管理(DB + FS 协调)和纯路径操作(直接 FS)是两种完全不同的职责。既要统一调用又要关注点分离,直接实现是矛盾的。

解法:统一调用入口 + handler 层分派。FileManager 作为唯一 lifecycle service 统管所有 IPC handler 注册,handler 内部按 target 类型分派到不同实现:

  • FileEntryId → FileManager 自身方法(entry 协调: resolve → DB + FS)
  • FilePath → ops.ts 纯函数(直接 FS/路径操作)

Tradeoff:纯路径操作(canWriteresolvePath 等)也交由 entry + FS 协调层管理,FileManager 承担了超出 entry 管理的 IPC 注册职责。但 handler 层只是 thin routing,其 public 方法签名仍然只认 FileEntryId,纯 path 操作不污染 public API。相比引入第二个 lifecycle service,这个代价更小。

Renderer
  → FileManager.registerIpcHandlers() (统一入口, handler 层分派)
    ├── target: FileEntryId → this.read / this.write / ... (entry 方法)
    └── target: FilePath    → ops.read / ops.write / ... (直接委托)

Main process 内部:其他 service 可根据实际需求直接调用 ops.ts 或 FileManager,不需要经过 IPC。

设计原则

  • 迁移保持语义不变:v1 → v2 迁移过程中保持已有行为不变,不改变调用方语义。例如 v1 的 deleteFile 是永久删除,v2 仍映射到 permanentDelete,不主动改为 trash。行为改进(如引入 trash)由后续需求驱动,不在迁移中混入
  • 统一入口,handler 分派:Renderer 只有一个 File IPC 入口,handler 按 FileEntryId / FilePath 分派到 FileManager 或 ops.ts
  • 不按 file/dir 拆分方法:v1 的 move / moveDir 等冗余合并
  • Renderer 只传必要信息:service 层推导元数据,不要求 renderer 预先获取
  • FileManager public API 只认 FileEntryId:纯路径操作在 handler 层直接委托 ops.ts,不经过 FileManager 方法

v1 清单与 v2 方案

状态标记:

  • ✅ 保留(可能改签名)
  • 🔀 合并到其他方法
  • ❌ 移除
  • ❓ 待定

A. 文件选择 / 对话框

v1 方法功能v2 方案说明
select打开文件选择对话框,返回 FileMetadata[]🔀 selectselectFolder 合并,通过 directory 参数区分。返回路径而非 FileMetadata(入库是 createEntry 的事)。单选返回 string | null,多选返回 string[]
selectFolder打开文件夹选择对话框,返回路径🔀 select合并到 select({ directory: true })
open打开文件对话框 + 读取内容(<2GB)拆为 select + read 组合,renderer 自行组装
save打开保存对话框 + 写入内容save保留,showSaveDialogshowOpenDialog 不同

v1 签名:

typescript
select(options?: OpenDialogOptions): Promise<FileMetadata[] | null>
selectFolder(options?: OpenDialogOptions): Promise<string | null>
open(options?: OpenDialogOptions): Promise<{ content: string; metadata: FileMetadata } | null>
save(path: string, content: string | NodeJS.ArrayBufferView, options?: any): Promise<string>

v2 签名:

typescript
// 选文件(单选)
select(options: { directory?: never; multiple?: false; filters?: FileFilter[]; title?: string }): Promise<string | null>
// 选文件(多选)
select(options: { directory?: never; multiple: true; filters?: FileFilter[]; title?: string }): Promise<string[]>
// 选文件夹(只能单选)
select(options: { directory: true; title?: string }): Promise<string | null>
// 保存对话框
save(options: { content: string | Uint8Array; defaultPath?: string; filters?: FileFilter[] }): Promise<string | null>

v1 兼容性审查:排查了所有 select/selectFolder/save 调用方,实际使用的 options 为 filtersproperties(映射为 multiple/directory)、title。v2 签名已全部覆盖。

B. 文件入库(写入 storage + 生成元数据)

v1 方法功能v2 方案说明
upload复制文件到 storage,MD5 去重,图片压缩🔀 createEntrycontent: FilePath
saveBase64Imagebase64 解码 → 写入 storage🔀 createEntrycontent: Base64String
savePastedImageUint8Array → 写入 storage,图片压缩🔀 createEntrycontent: Uint8Array
download从 URL 下载 → 写入 storage🔀 createEntrycontent: URLString,main 负责下载
batchUploadMarkdown批量复制 .md 到目标目录🔀 batchCreateEntries泛化为批量创建,不限 markdown

v1 签名:

typescript
upload(file: FileMetadata): Promise<FileMetadata>
saveBase64Image(data: string): Promise<FileMetadata>
savePastedImage(imageData: Uint8Array, extension?: string): Promise<FileMetadata>
download(url: string, isUseContentType?: boolean): Promise<FileMetadata>
batchUploadMarkdown(filePaths: string[], targetPath: string): Promise<{ fileCount: number; folderCount: number; skippedFiles: string[] }>

v2 签名:

typescript
type FilePath = `/${string}` | `${string}:${string}` | `file://${string}`
type Base64String = `data:${string};base64,${string}`
type URLString = `http://${string}` | `https://${string}`
type FileContent = FilePath | Base64String | URLString | Uint8Array

type CreateEntryParams =
  | { type: 'file'; parentId: FileEntryId; name: string; content: FileContent }
  | { type: 'dir'; parentId: FileEntryId; name: string }

createEntry(params: CreateEntryParams): Promise<FileEntry>
// 批量创建文件条目(仅文件,不支持目录)
batchCreateEntries(params: { parentId: FileEntryId; items: Array<{ name: string; content: FileContent }> }): Promise<BatchOperationResult>

v1 兼容性审查upload 实际调用方全部传的是路径(select 返回或 getPathForFile)。 saveBase64Image 用于 AI 生图(base64)。savePastedImage 用于富文本编辑器粘贴(Uint8Array)。 download 用于 AI 生图(URL)。savePastedImage 用于富文本编辑器粘贴(Uint8Array), v2 用 createEntry({ parentId: 'mount_temp', content: uint8Array }) 替代,临时文件纳入条目系统。 batchUploadMarkdown 唯一调用方为 NotesService.ts, 传入本地路径数组 + 目标目录,返回值中 NotesPage 只用 fileCount === 0 判断是否成功, 可用 BatchOperationResult.succeeded.length === 0 替代。folderCount(自动创建的目录数) 和 skippedFiles(跳过的非 markdown 文件数)在 NotesPage 中完全没有使用。 skippedFiles 的过滤逻辑由 renderer 在调用 batchCreateEntries 前完成。 全部场景被 FileContent 联合类型覆盖。

C. 文件读取(从 storage 或外部路径)

v1 方法功能v2 方案说明
read按 fileId 读内容(支持 doc/pdf/xlsx 提取)🔀 read统一入口,FileEntryId | FilePath 自动区分
readExternal按外部路径读内容🔀 read合并到 read,传 FilePath
get按路径获取 FileMetadata🔀 getMetadatav2 用 getMetadata 替代
base64Image按 fileId 读图片为 base64🔀 readencoding: 'base64' 重载
binaryImage按 fileId 读图片为 Buffer🔀 readencoding: 'binary' 重载
base64File按 fileId 读文件为 base64🔀 readencoding: 'base64' 重载
pdfInfo按 fileId 读 PDF 页数🔀 getMetadataPdfMetadata.pageCount

v1 签名:

typescript
read(fileId: string, detectEncoding?: boolean): Promise<string>
readExternal(filePath: string, detectEncoding?: boolean): Promise<string>
get(filePath: string): Promise<FileMetadata | null>
base64Image(fileId: string): Promise<{ mime: string; base64: string; data: string }>
binaryImage(fileId: string): Promise<{ data: Buffer; mime: string }>
base64File(fileId: string): Promise<{ data: string; mime: string }>
pdfInfo(fileId: string): Promise<number>

v2 签名:

typescript
// ─── read: 统一文件内容读取 ───

// 图片变换参数(可选,非图片文件传入时静默忽略)
// 调用方有责任确认目标文件类型,service 层不做额外校验
// 动机:#14062 — 发送图片到 LLM API 前自动压缩,避免超大 base64 payload
// 具体字段待调研 sharp API 后确定
type ImageTransform = {
  maxDimension?: number
  quality?: number
  format?: string
}

// text(默认)
read(target: FileEntryId | FilePath, options?: { encoding?: 'text'; detectEncoding?: boolean }): Promise<string>
// base64(支持图片压缩)
read(target: FileEntryId | FilePath, options: { encoding: 'base64'; imageTransform?: ImageTransform }): Promise<{ data: string; mime: string }>
// binary(支持图片压缩)
read(target: FileEntryId | FilePath, options: { encoding: 'binary'; imageTransform?: ImageTransform }): Promise<{ data: Uint8Array; mime: string }>

// ─── getMetadata: 文件元信息(按类型返回不同字段) ───
type MetadataBase = { size: number; createdAt: number; modifiedAt: number }

// 第一层:kind = 'file' | 'directory'
type DirectoryMetadata = MetadataBase & { kind: 'directory' }
type FileMetadataCommon = MetadataBase & { kind: 'file'; mime: string }

// 第二层(仅 file):type = 'image' | 'pdf' | 'text' | 'other'
type ImageFileMetadata = FileMetadataCommon & { type: 'image'; width: number; height: number }
type PdfFileMetadata = FileMetadataCommon & { type: 'pdf'; pageCount: number }
type TextFileMetadata = FileMetadataCommon & { type: 'text'; encoding: string }
type GenericFileMetadata = FileMetadataCommon & { type: 'other' }

type FileKindMetadata = ImageFileMetadata | PdfFileMetadata | TextFileMetadata | GenericFileMetadata
type FileMetadata = DirectoryMetadata | FileKindMetadata

getMetadata(target: FileEntryId | FilePath): Promise<FileMetadata>

v1 兼容性审查

  • read:传 file.id + file.ext 拼接字符串(如 "abc123.pdf")或配置文件名('custom-minapps.json')。 v2 用 FileEntryId 不需要拼 ext;配置文件名场景走 FilePath
  • readExternal:全部传绝对路径(笔记文件、外部文件),v2 FilePath 覆盖。
  • get:返回 FileMetadata 用于 UI 预览(PasteService、拖拽、TranslatePage)。v2 getMetadata 返回结构不同但信息更全面,调用方需适配。
  • base64Image / binaryImage / base64File:全部传 file.id + file.ext,v2 改传 FileEntryId。 新增 imageTransform 可选参数(#14062),AI 调用层可统一传参压缩大图, sharp 已是项目依赖,service 层直接调用。非图片文件传入 imageTransform 时静默忽略。
  • pdfInfo:renderer 中零调用,可安全移除。getMetadataPdfMetadata.pageCount 作为备用保留。

D. 文件删除

v1 方法功能v2 方案说明
delete按 fileId 删 storage 文件🔀 trash / permanentDelete通过 FileEntryId 操作,不区分 file/dir
deleteDir按 ID 删 storage 目录🔀 trash / permanentDeleterenderer 零调用,合并
deleteExternalFile删外部路径文件🔀 permanentDelete笔记纳入条目系统后由 service 按 mount type 处理
deleteExternalDir删外部路径目录🔀 permanentDelete同上
clear清空整个 storage 目录renderer 零调用,移除

v1 签名:

typescript
delete(fileId: string): Promise<void>
deleteDir(dirPath: string): Promise<void>
deleteExternalFile(filePath: string): Promise<void>
deleteExternalDir(dirPath: string): Promise<void>
clear(spanContext?: SpanContext): Promise<void>

v2 签名:

typescript
trash(params: { id: FileEntryId }): Promise<void>
restore(params: { id: FileEntryId }): Promise<FileEntry>
permanentDelete(params: { id: FileEntryId }): Promise<void>
batchTrash(params: { ids: FileEntryId[] }): Promise<BatchOperationResult>
batchRestore(params: { ids: FileEntryId[] }): Promise<BatchOperationResult>
batchPermanentDelete(params: { ids: FileEntryId[] }): Promise<BatchOperationResult>

v1 兼容性审查

  • deleteuseKnowledge.tsFileManager.ts 使用,传 file.nameid + ext。v2 改传 FileEntryId
  • deleteDir:renderer 零调用。
  • deleteExternalFile/deleteExternalDir:仅 NotesService.ts 使用,传 entry.externalPath。v2 笔记纳入条目系统后统一走 permanentDelete(entryId)
  • clear:renderer 零调用,仅在 preload/ipc.ts 注册。安全移除。

E. 文件移动 / 重命名

v1 方法功能v2 方案说明
move按路径移动文件🔀 move统一用 FileEntryId,不区分 file/dir
moveDir按路径移动目录🔀 move合并
rename按路径重命名文件(加 .md)🔀 moverename = 同目录 move + newName
renameDir按路径重命名目录🔀 move合并

v1 签名:

typescript
move(path: string, newPath: string): Promise<void>
moveDir(dirPath: string, newDirPath: string): Promise<void>
rename(path: string, newName: string): Promise<void>
renameDir(dirPath: string, newName: string): Promise<void>

v2 签名:

typescript
// move + rename 合并:newName 可选,省略则保持原名
move(params: { id: FileEntryId; targetParentId: FileEntryId; newName?: string }): Promise<FileEntry>
batchMove(params: { ids: FileEntryId[]; targetParentId: FileEntryId }): Promise<BatchOperationResult>

v1 兼容性审查

  • move/moveDir:仅 NotesPage.tsx 使用,按 entry.type 分别调用,传 externalPath。v2 统一 move(entryId, targetParentId)
  • rename/renameDir:仅 NotesService.ts 使用,按 isFile 分别调用,传 externalPath + safeName。v2 统一 move(entryId, 原parentId, newName)

F. 底层 FS 操作

v1 方法功能v2 方案说明
write按外部路径写入 bytes/stringwrite笔记保存、导出等场景仍需直接写外部路径,不经过条目系统
writeWithId按 fileId 写入 storage🔀 write合并到 write,传 FileEntryId 或 FilePath
mkdir创建目录🔀 createEntryv2 创建目录走 createEntry({ type: 'dir' })
copy从 storage 复制到外部路径copy当前零调用,但文件管理器基本操作,提前设计
createTempFile生成临时文件路径(不创建文件)粘贴场景被 createEntry({ content: Uint8Array }) 替代

v1 签名:

typescript
write(filePath: string, data: Uint8Array | string): Promise<void>
writeWithId(id: string, content: string): Promise<void>
mkdir(dirPath: string): Promise<string>
copy(fileId: string, destPath: string): Promise<void>
createTempFile(fileName: string): Promise<string>

v2 签名:

typescript
// 写内容到指定目标(条目或外部路径),不创建新条目
write(target: FileEntryId | FilePath, data: string | Uint8Array): Promise<void>
// 树内复制(创建新条目 + 物理复制)
copy(params: { id: FileEntryId; targetParentId: FileEntryId; newName?: string }): Promise<FileEntry>
// 导出到外部路径(不创建新条目)
copy(params: { id: FileEntryId; destPath: FilePath }): Promise<void>

v1 兼容性审查

  • write:PasteService(落盘粘贴数据,v2 被 createEntry 替代)、NotesService/NotesPage(写笔记内容)、export.ts(导出 markdown)、exportExcel.ts(写 Excel)、HtmlArtifactsCard(临时 HTML)。笔记和导出场景仍需 write
  • writeWithId:仅 minapps 配置文件读写(custom-minapps.json)。v2 用 write(FileEntryId | FilePath, ...)
  • mkdir:仅 NotesService 创建笔记子目录。v2 走 createEntry({ type: 'dir' })
  • copy:renderer 零调用,安全移除。
  • createTempFile:粘贴场景被 createEntry({ parentId: 'mount_temp', content }) 替代。 临时文件纳入条目系统,粘贴时创建临时 FileRef(sourceType: 'temp_session'), mount_temp 兼作临时文件和缓存。ref 由调用方显式管理(发送时删临时 ref + 创建正式 ref + move, 取消时删 ref)。清理器只自动删除无 ref 的条目(启动时 + 定期),绝不删 ref。 用户通过删 ref 主动释放不需要的缓存。 HTML 预览可用 write 写 temp 路径。(?)

G. 文件检测 / 校验

v1 方法功能v2 方案说明
isTextFile检测是否文本文件🔀 getMetadatametadata.type === 'text' 判断,不单独保留
isDirectory检测是否目录🔀 getMetadata条目用 entry.type,外部路径用 getMetadata 判断
checkFileName文件名消毒(sanitize)+ 目标路径冲突检测❌ (拆分)sanitize 提取为 shared 纯函数(不需要 IPC),冲突检测由 createEntry/move 内部处理
validateNotesDirectory校验笔记目录合法性validateNotesPathnotes 专用,暂不泛化。app 内部只允许 Data/files/notes/

v1 签名:

typescript
isTextFile(filePath: string): Promise<boolean>
isDirectory(filePath: string): Promise<boolean>
checkFileName(dirPath: string, fileName: string, isFile: boolean): Promise<{ safeName: string; exists: boolean }>
validateNotesDirectory(dirPath: string): Promise<boolean>

v2 签名:

typescript
// 已在 C 组定义
getMetadata(target: FileEntryId | FilePath): Promise<FileMetadata>
// 验证路径是否适合作为 notes 目录
// notes 专用:app 内部只允许 Data/files/notes/,禁止指向其他 mount 目录
validateNotesPath(dirPath: FilePath): Promise<boolean>

v1 兼容性审查

  • isTextFileutils/file.tsAttachmentPreview.tsx 使用。v2 用 getMetadata(path).type === 'text' 替代。
  • isDirectory:仅 SkillsSettings.tsx 拖拽安装判断。v2 用 getMetadata(path) 判断。
  • checkFileName:仅 NotesService.ts/NotesPage.tsx 使用(4 处),用于创建/重命名前校验。v2 由 createEntry/move 的 service 内部校验,冲突时抛错误,renderer 捕获并提示用户。
  • validateNotesDirectoryNotesService.ts/NotesSettings.tsx 使用。v2 改为 validateNotesPath, 主要改动:将硬编码受限路径(filesDir/appDataPath)替换为"app 内部只允许 Data/files/notes/", 新增禁止指向其他 mount basePath(managed/temp/ 等)。其余检查(存在、可写、非系统根、非当前路径)不变。 | validateNotesDirectory | 校验笔记目录合法性 | ❓ | |

H. 系统操作

v1 方法功能v2 方案说明
openPath用系统默认程序打开文件/目录open接收 FileEntryId | FilePath,service resolve 物理路径
openFileWithRelativePath用相对路径打开 storage 文件🔀 open合并,v2 传 FileEntryId
showInFolder在文件管理器中显示showInFolder接收 FileEntryId | FilePath
getPathForFilewebUtils.getPathForFile(preload 直接调)✅ 移出非 IPC,通过 contextBridge 暴露的同步工具方法,不属于 FileManager

v1 签名:

typescript
openPath(path: string): Promise<void>
openFileWithRelativePath(file: FileMetadata): Promise<void>
showInFolder(path: string): Promise<void>
getPathForFile(file: File): string

v2 签名:

typescript
// 用系统默认程序打开文件/目录
open(target: FileEntryId | FilePath): Promise<void>
// 在系统文件管理器中显示
showInFolder(target: FileEntryId | FilePath): Promise<void>
// 移至 preload utils,不属于 FileManager IPC
// getPathForFile(file: File): string

v1 兼容性审查

  • openPath:多处使用(知识库目录、文件列表、引用链接、agent 工具路径),传外部路径。v2 open 支持 FilePath 覆盖。
  • openFileWithRelativePath:仅知识库文件/视频使用,传 FileMetadata(内部拼 storage 路径)。v2 传 FileEntryId,service resolve 物理路径。
  • showInFolder:仅 ClickableFilePath.tsx 使用,传路径。v2 支持 FileEntryId | FilePath
  • getPathForFile:多处使用(PasteService、拖拽、知识库文件),preload 直接调 webUtils,不变。

I. 目录扫描

v1 方法功能v2 方案说明
getDirectoryStructure递归扫描目录树v2 笔记纳入条目系统,用 DataApi children 查询替代
listDirectoryripgrep 搜索目录内容listDirectoryagent 工具面板列出外部目录文件,非条目系统管理,仍需保留

v1 签名:

typescript
getDirectoryStructure(dirPath: string): Promise<NotesTreeNode[]>
listDirectory(dirPath: string, options?: DirectoryListOptions): Promise<string[]>

v2 签名:

typescript
// 列出外部目录内容(非条目系统管理的目录)
listDirectory(dirPath: FilePath, options?: DirectoryListOptions): Promise<string[]>  // DirectoryListOptions 维持原样

v1 兼容性审查

  • getDirectoryStructure:仅 Notes 使用(加载树、检查目录内容)。v2 笔记纳入条目系统后,用 GET /files/entries/:id/children 替代。
  • listDirectory:仅 useResourcePanel.tsx 使用,列出 agent 可访问目录的文件。传外部路径 + options。v2 保留,签名基本不变。

J. File Watcher

v1 方法功能v2 方案说明
startFileWatcher启动 chokidar 监听v2 由 FileManager service 内部管理 local_external mount 的 watcher
stopFileWatcher停止监听同上,service 跟随 mount 生命周期自动管理
pauseFileWatcher暂停监听(批量操作时)同上,service 在批量操作时内部暂停
resumeFileWatcher恢复监听同上
onFileChangerenderer 监听变更事件v2 renderer 通过 DataApi 数据订阅感知变更,不直接监听 FS 事件

v1 签名:

typescript
startFileWatcher(dirPath: string, config?: any): Promise<void>
stopFileWatcher(): Promise<void>
pauseFileWatcher(): Promise<void>
resumeFileWatcher(): Promise<void>
onFileChange(callback: (data: FileChangeEvent) => void): () => void

v2 签名:

无。Watcher 由 FileManager service 内部管理,不暴露 IPC。

v1 兼容性审查

  • 全部仅 Notes 使用(NotesPage.tsxNotesService.ts)。
  • v2 笔记纳入条目系统后,local_external mount 的 watcher 由 FileManager service 内部管理: FS 变更 → service 自动同步到 DB → renderer 通过 DataApi 数据订阅感知。
  • 批量操作时的 pause/resume 也由 service 内部协调,renderer 无需关心。

v2 FileManager IPC 完整方法列表

v1 44 个方法 → v2 19 个方法(含 1 个 preload 工具方法)。

类型定义

typescript
type FilePath = `/${string}` | `${string}:${string}` | `file://${string}`;
type Base64String = `data:${string};base64,${string}`;
type URLString = `http://${string}` | `https://${string}`;
type FileContent = FilePath | Base64String | URLString | Uint8Array;

type CreateEntryParams =
  | { type: "file"; parentId: FileEntryId; name: string; content: FileContent }
  | { type: "dir"; parentId: FileEntryId; name: string };

type MetadataBase = { size: number; createdAt: number; modifiedAt: number };
type DirectoryMetadata = MetadataBase & { kind: "directory" };
type FileMetadataCommon = MetadataBase & { kind: "file"; mime: string };
type ImageFileMetadata = FileMetadataCommon & {
  type: "image";
  width: number;
  height: number;
};
type PdfFileMetadata = FileMetadataCommon & { type: "pdf"; pageCount: number };
type TextFileMetadata = FileMetadataCommon & { type: "text"; encoding: string };
type GenericFileMetadata = FileMetadataCommon & { type: "other" };
type FileKindMetadata =
  | ImageFileMetadata
  | PdfFileMetadata
  | TextFileMetadata
  | GenericFileMetadata;
type FileMetadata = DirectoryMetadata | FileKindMetadata;

type BatchOperationResult = {
  succeeded: FileEntryId[];
  failed: Array<{ id: FileEntryId; error: string }>;
};

// 图片读取时可选变换(#14062),非图片文件传入时静默忽略,具体字段待调研 sharp API
type ImageTransform = {
  maxDimension?: number;
  quality?: number;
  format?: string;
};

方法签名

typescript
// ─── A. 文件选择 / 对话框 ───
select(options: { directory?: never; multiple?: false; filters?: FileFilter[]; title?: string }): Promise<string | null>
select(options: { directory?: never; multiple: true; filters?: FileFilter[]; title?: string }): Promise<string[]>
select(options: { directory: true; title?: string }): Promise<string | null>
save(options: { content: string | Uint8Array; defaultPath?: string; filters?: FileFilter[] }): Promise<string | null>

// ─── B. 条目创建 ───
createEntry(params: CreateEntryParams): Promise<FileEntry>
batchCreateEntries(params: { parentId: FileEntryId; items: Array<{ name: string; content: FileContent }> }): Promise<BatchOperationResult>

// ─── C. 文件读取 / 元信息 ───
read(target: FileEntryId | FilePath, options?: { encoding?: 'text'; detectEncoding?: boolean }): Promise<string>
read(target: FileEntryId | FilePath, options: { encoding: 'base64'; imageTransform?: ImageTransform }): Promise<{ data: string; mime: string }>
read(target: FileEntryId | FilePath, options: { encoding: 'binary'; imageTransform?: ImageTransform }): Promise<{ data: Uint8Array; mime: string }>
getMetadata(target: FileEntryId | FilePath): Promise<FileMetadata>

// ─── D. 条目删除 ───
trash(params: { id: FileEntryId }): Promise<void>
restore(params: { id: FileEntryId }): Promise<FileEntry>
permanentDelete(params: { id: FileEntryId }): Promise<void>
batchTrash(params: { ids: FileEntryId[] }): Promise<BatchOperationResult>
batchRestore(params: { ids: FileEntryId[] }): Promise<BatchOperationResult>
batchPermanentDelete(params: { ids: FileEntryId[] }): Promise<BatchOperationResult>

// ─── E. 条目移动(含重命名) ───
move(params: { id: FileEntryId; targetParentId: FileEntryId; newName?: string }): Promise<FileEntry>
batchMove(params: { ids: FileEntryId[]; targetParentId: FileEntryId }): Promise<BatchOperationResult>

// ─── F. 文件写入 / 复制 ───
write(target: FileEntryId | FilePath, data: string | Uint8Array): Promise<void>
copy(params: { id: FileEntryId; targetParentId: FileEntryId; newName?: string }): Promise<FileEntry>
copy(params: { id: FileEntryId; destPath: FilePath }): Promise<void>

// ─── G. 校验 / 路径工具 ───
validateNotesPath(dirPath: FilePath): Promise<boolean>
canWrite(dirPath: FilePath): Promise<boolean>
resolvePath(filePath: string): Promise<string>
isPathInside(childPath: string, parentPath: string): Promise<boolean>
isNotEmptyDir(dirPath: FilePath): Promise<boolean>

// ─── H. 系统操作 ───
open(target: FileEntryId | FilePath): Promise<void>
showInFolder(target: FileEntryId | FilePath): Promise<void>

// ─── I. 目录扫描 ───
listDirectory(dirPath: FilePath, options?: DirectoryListOptions): Promise<string[]>  // 维持原样

不在 FileManager IPC 中的方法

  • getPathForFile(file: File): string — preload 通过 contextBridge 暴露的同步工具方法,不属于 FileManager
  • File Watcher(start/stop/pause/resume/onFileChange)— v2 由 FileManager service 内部管理,不暴露 IPC
  • getDirectoryStructure — v2 用 DataApi GET /files/entries/:id/children 替代
  • checkFileName — sanitize 提取为 shared 纯函数,冲突检测由 service 内部处理

非 File_ 前缀的文件相关 IPC

v1 还有一些散落在其他命名空间下的文件相关 IPC,需要统一分析归属。

已合并到 File Module IPC

v1 IPCv1 实现v2 方案说明
Fs_ReadFileService.readFile🔀 read(FilePath)→ ops.read(双态方法的 FilePath 路径)
Fs_ReadTextFileService.readTextFileWithAutoEncoding🔀 read(FilePath, { encoding: 'text' })同上
Open_Pathshell.openPath(path)🔀 open(FilePath)File_OpenPath 完全重复,→ ops.open
App_HasWritePermissionhasWritePermission(filePath)🔀 canWrite(FilePath)→ ops.canWrite
App_ResolvePathpath.resolve(untildify(filePath))🔀 resolvePath(FilePath)→ ops.resolvePath
App_IsPathInsideisPathInside(childPath, parentPath)🔀 isPathInside(child, parent)→ ops.isPathInside
App_IsNotEmptyDirfs.readdirSync(path).length > 0🔀 isNotEmptyDir(FilePath)→ ops.isNotEmptyDir

v1 兼容性审查

  • Fs_Read:aiCore 和 renderer 中用于读取外部文件(URL 或本地路径),v2 read(FilePath) 覆盖。
  • Fs_ReadText:renderer 中用于读取文本文件并自动检测编码,v2 read(FilePath, { encoding: 'text', detectEncoding: true }) 覆盖。
  • Open_Path:多处使用(知识库、导出结果等),与 File_OpenPath 实现完全相同(均调用 shell.openPath),v2 统一为 open(FilePath)
  • App_HasWritePermission:数据迁移选择目录时校验权限。通用能力,validateNotesPath 内部也需要。
  • App_ResolvePath / App_IsPathInside:纯路径计算,无 FS I/O。renderer 无 node:path,仍需 IPC。
  • App_IsNotEmptyDir:数据迁移校验目录。通用能力,归入 ops。

保持独立(不属于 File Module)

v1 IPCv1 实现v2 方案说明
Pdf_ExtractTextextractPdfText(data: Uint8Array | ArrayBuffer | string)✅ 保持独立纯内容处理(传 buffer),不依赖文件系统或 entry 系统

不属于 FileManager(各自业务模块)

v1 IPC说明v2 归属
Open_Websiteshell.openExternal(url) — URL 不是文件操作App 层
FileService_Upload/List/Delete/RetrieveAI Provider 远程文件 API(Gemini 等)Provider 模块
Gemini_UploadFile/Base64File/RetrieveFile/ListFiles/DeleteFileGemini 专用文件操作Provider 模块
Export_WordWord 导出Export 模块
Zip_Compress/Decompress压缩解压Backup 模块
Webview_PrintToPDF/SaveAsHTMLWebview 输出Webview 模块
Skill_ReadFile/ListFilesSkill 文件读取Skill 模块