动机
便携版当前所有持久化数据(数据库、日志、配置、BT 缓存、插件、组件等)直接散落在 exe 根目录,与 fluxdown.exe、DLL、Flutter data/ 捆绑资源混在一起。安装版则有独立的 %LOCALAPPDATA%/FluxDown/ 目录。
方案
便携模式下,数据统一进 <exe_dir>/portable_data/ 子目录,标记文件 portable 保持在根层不动。
FluxDown/ (解压后 - exe 根目录)
├── data/ ← Flutter 捆绑资源(不动)
├── portable_data/ ← 新增:所有持久化数据集中于此
│ ├── flux_down.db
│ ├── settings.json
│ ├── logs/
│ ├── bt_session/
│ ├── plugins/
│ ├── plugins-work/
│ └── bin/
├── portable ← 标记文件(不动)
├── fluxdown.exe
└── *.dll
实现范围
两个文件,各 ~25 行:
| 端 |
文件 |
改动 |
| Rust |
native/engine/src/data_dir.rs |
便携路径 exe_dir() → exe_dir().join("portable_data") + migrate_portable_data() 迁移函数 |
| Dart |
lib/src/services/platform_utils.dart |
同上,两端常量和迁移逻辑完全对称 |
所有下游消费者(DB、日志、KvStore、BT、组件路径)都通过 resolve_data_dir()/resolveDataDir() 取路径后 join("子目录"),改入口一行全局生效。
迁移策略
首次启动时自动将旧版数据从 exe 根层移动到 portable_data/。迁移项:
flux_down.db(含 -wal/-shm)、settings.json、logs/、bt_session/、plugins/、plugins-work/、bin/
- 幂等:目标已存在则跳过(
rename 而非 copy+remove,失败不丢数据)
- 安全:失败只打
eprintln,不抛异常,不阻塞启动
- 竞态安全:Rust 和 Dart 各自检查
exists() 后原子 rename,先到先得
Scoop 兼容(关键)
Scoop manifest 涉及两处:ExLei/Scoop-Bucket(自有)和 ScoopInstaller/Extras(PR #18232 审核中)。PR 审核建议使用 $persist_dir 复制进出模式(参见 PR 中 CodeRabbit 的 review)。
当前 persist 是根层零散文件的硬链接模式:
"persist": ["flux_down.db", "bt_session", "settings.json", "logs"]
升级路径分析:
Scoop 的 persist 机制在升级时按列表在新旧版本目录间建立硬链接(文件)/符号链接(目录)。如果把 persist 直接改成 ["portable_data"],旧版本(0.2.x)没有 portable_data/ 目录 → 无物可迁 → 用户数据丢失。
第一阶段(本次发布)—— 保留旧 persist + 新增 portable_data:
"persist": [
"flux_down.db",
"bt_session",
"settings.json",
"logs",
"portable_data"
],
"pre_install": [
"# 确保 portable_data 目录存在,使得 Scoop persist 将其识别为可持久化目录",
"if (!(Test-Path \"$dir\\portable_data\")) { New-Item -ItemType Directory \"$dir\\portable_data\" -Force | Out-Null }"
]
升级流程:
- 旧
persist 项由 Scoop 创建硬链接 → 根层 flux_down.db 等指向 $persist_dir/fluxdown/ 中的真实数据
- 新增
portable_data persist → Scoop 创建目录 Junction → $dir/portable_data/ → $persist_dir/fluxdown/portable_data/(首次为空)
- 应用启动 → Rust/Dart 迁移代码检测根层硬链接 →
rename 到 portable_data/ 内
rename 移动硬链接本身(不拷贝数据,同 inode)→ 数据物理位置不变,目录项从 $dir/ 迁移到 $dir/portable_data/,由于后者是 Junction,实际写入 $persist_dir/fluxdown/portable_data/
- 根层旧硬链接路径(
$persist_dir/fluxdown/flux_down.db)仍然存在指向同 inode,冗余但无害
全新安装:根层无旧文件 → 迁移 no-op → portable_data/ 直接使用。
第二阶段(后续版本)—— 清理:
"persist": ["portable_data"]
此时 $persist_dir/fluxdown/portable_data/ 已包含所有迁移后的数据,可安全移除旧 persist 项。
调用链影响(CodeGraph 验证)
resolve_data_dir/resolveDataDir 共 6 个调用者,全部是 data_dir.join("子目录") 模式,改入口即可自动传播:
- Rust:
create_actors() → download_actor.run → Engine::new / Db::open / DownloadManagerConfig
- Rust:
logger::init() → data_dir/logs/
- Rust:
cli::run_add_local() → Engine::new
- Dart:
KvStore.init() → data_dir/settings.json
- Dart:
LogService._resolveLogDir() → data_dir/logs/
- Dart:
AppIconService._iconsDir() → data_dir/icons/
待合并前补充
类型
AFK
动机
便携版当前所有持久化数据(数据库、日志、配置、BT 缓存、插件、组件等)直接散落在 exe 根目录,与
fluxdown.exe、DLL、Flutterdata/捆绑资源混在一起。安装版则有独立的%LOCALAPPDATA%/FluxDown/目录。方案
便携模式下,数据统一进
<exe_dir>/portable_data/子目录,标记文件portable保持在根层不动。实现范围
两个文件,各 ~25 行:
native/engine/src/data_dir.rsexe_dir()→exe_dir().join("portable_data")+migrate_portable_data()迁移函数lib/src/services/platform_utils.dart所有下游消费者(DB、日志、KvStore、BT、组件路径)都通过
resolve_data_dir()/resolveDataDir()取路径后join("子目录"),改入口一行全局生效。迁移策略
首次启动时自动将旧版数据从 exe 根层移动到
portable_data/。迁移项:flux_down.db(含-wal/-shm)、settings.json、logs/、bt_session/、plugins/、plugins-work/、bin/rename而非copy+remove,失败不丢数据)eprintln,不抛异常,不阻塞启动exists()后原子rename,先到先得Scoop 兼容(关键)
Scoop manifest 涉及两处:ExLei/Scoop-Bucket(自有)和 ScoopInstaller/Extras(PR #18232 审核中)。PR 审核建议使用
$persist_dir复制进出模式(参见 PR 中 CodeRabbit 的 review)。当前 persist 是根层零散文件的硬链接模式:
升级路径分析:
Scoop 的 persist 机制在升级时按列表在新旧版本目录间建立硬链接(文件)/符号链接(目录)。如果把 persist 直接改成
["portable_data"],旧版本(0.2.x)没有portable_data/目录 → 无物可迁 → 用户数据丢失。第一阶段(本次发布)—— 保留旧 persist + 新增 portable_data:
升级流程:
persist项由 Scoop 创建硬链接 → 根层flux_down.db等指向$persist_dir/fluxdown/中的真实数据portable_datapersist → Scoop 创建目录 Junction →$dir/portable_data/→$persist_dir/fluxdown/portable_data/(首次为空)rename到portable_data/内rename移动硬链接本身(不拷贝数据,同 inode)→ 数据物理位置不变,目录项从$dir/迁移到$dir/portable_data/,由于后者是 Junction,实际写入$persist_dir/fluxdown/portable_data/$persist_dir/fluxdown/flux_down.db)仍然存在指向同 inode,冗余但无害全新安装:根层无旧文件 → 迁移 no-op →
portable_data/直接使用。第二阶段(后续版本)—— 清理:
此时
$persist_dir/fluxdown/portable_data/已包含所有迁移后的数据,可安全移除旧 persist 项。调用链影响(CodeGraph 验证)
resolve_data_dir/resolveDataDir共 6 个调用者,全部是data_dir.join("子目录")模式,改入口即可自动传播:create_actors()→ download_actor.run → Engine::new / Db::open / DownloadManagerConfiglogger::init()→ data_dir/logs/cli::run_add_local()→ Engine::newKvStore.init()→ data_dir/settings.jsonLogService._resolveLogDir()→ data_dir/logs/AppIconService._iconsDir()→ data_dir/icons/待合并前补充
migrate_portable_data单元测试(幂等性、混合文件+目录、目标已存在跳过、硬链接 rename 场景)flux_down.db-wal被锁 → 跳过而非崩溃)类型
AFK