TauriTavern/docs/CurrentState/FileTransfer.md
Darkatse f8bcdd6030 fix(files): let Android name saved files by its own type table
2.4.0 moved Android exports to the system save dialog and typed them
with mime_guess, a table Android does not use. Unknown extensions such
as .jsonl became application/octet-stream, which ColorOS saves as .bin.

Android now passes the extension like HarmonyOS, so the dialog plugin
maps it with MimeTypeMap and leaves unknown extensions untyped.

Fixes #392.

Validation: pnpm run check; Android aarch64 cargo check.
2026-10-22 16:51:54 -04:00

55 lines
3.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 文件导入与导出
宿主提供两个能力:**选取**用户文件,以及**交付**已经生成完毕的文件。功能代码提供用途、内容和建议文件名;系统面板、URI 授权与文件复制集中在 host 的 `platform/file_transfer`。
## 1. 规则
- **谁产出文件,谁发起交付。** Rust 产出的文件由对应命令交付;前端发起的交付先暂存内容(WebView 字节,或宿主下载的远程资源),再调用 `deliver_staged_file`。
- **交付消费源文件。** 保存、取消或失败都会结束本次产物。
- **交付结果为 `{ delivered }`。** 完成目标写入或系统交接后为 `true`,取消为 `false`,失败抛错。目标是普通文件时,同步落盘后才算完成。
- **选取按用途过滤。** 角色卡、Skill 归档、数据归档分别映射为各平台的过滤条件;文件内容由导入方校验。
## 2. 平台实现
| 平台 | 选取 | 交付 |
| --- | --- | --- |
| Windows / macOS / Linux | dialog 插件返回路径,复制进暂存;数据归档原地使用 | 保存面板;同卷时移动,跨卷时复制 |
| Android / OpenHarmony | dialog 插件返回带授权的 URI,通过 fs 插件打开 fd 后复制进暂存 | 保存面板授权目标 URI,通过 fd 写入 |
| iOS | Document Picker 生成副本,移动进暂存 | Share Sheet |
Android 的 content URI 不带文件名,由 `ContentUriPlugin` 查询 `DISPLAY_NAME` 得到。
Android 与鸿蒙的保存面板只接收文件扩展名,类型由系统按自己的表转换;系统不认识的扩展名不声明具体类型。保存面板会按类型推导后缀,自行指定的类型可能与系统的表不一致,例如 ColorOS 会把 `application/octet-stream` 的 `.jsonl` 存成 `.bin`。
## 3. 暂存
| 暂存 | 位置 | 内容 |
| --- | --- | --- |
| 通用 | `app_cache/tauritavern-staging/<kind>/` | WebView 字节(上传与 Blob 导出)、宿主下载的远程资源、选取的角色卡与 Skill 归档、调试包 |
| 归档 | `archive_imports_root/incoming`、`archive_exports_root` | 数据归档、用户备份 |
- WebView 字节经 `stage_file_*` 进入宿主:Android 用 base64 帧,其余平台用原始字节。
- 前端经宿主 `safeInvoke` 调用暂存与交付命令,失败时得到可读的错误消息;只有二进制分块走原始 invoke。
- 远程资源经 `stage_file_from_url` 进入宿主:由 `ContentService::download_external_file` 下载,与外部内容导入共用 URL 规则和 `Download` 配置(含用户的请求代理),不带页面的 Cookie 与 Referer。
- 写入失败的暂存文件由写入方删除。通用暂存在宿主启动时清空,暂存后未进入交付的文件随之回收;归档暂存沿用过期清扫。
- 归档单独暂存:Android 的数据根与 cache 不在同一个卷,大型归档要放在数据卷上,才能直接移动进任务工作区。
## 4. 入口
| 产物或来源 | 入口 |
| --- | --- |
| `download()`、同源 `a[download]`、Android 图片长按(页面可读来源)、Skill 与 Agent 导出 | `deliverBlob()` |
| Android 图片长按(远程图片) | `deliverRemoteFile()`:`stage_file_from_url` 后调用 `deliver_staged_file` |
| 数据归档导出 | 任务完成后调用 `save_export_data_archive`:领取产物,交付,再标记为已处置 |
| 数据归档导入 | `import_data_archive_from_picker` |
| 用户备份 | `export_user_backup_archive` |
| 调试包 | `devlog_export_bundle` |
| 角色卡、Skill 归档选取 | `pick_import_files` |
`<input type="file">`、拖放和桌面目录选择沿用原有入口。新增的导入导出功能接入上述入口。
## 5. 已知限制
- 移动端 dialog 插件会把面板失败也报告为取消。
- 写入中途失败时,目标文件可能不完整;Android 与鸿蒙的 fs 插件不能删除 URI 目标。
- 内容要在保存面板出现前完成暂存,期间不显示进度;远程下载没有总超时,也不能取消。