Skip to content

✨ [Feature] 便携版数据目录整合到 portable_data/ #91

Description

@ExLei

动机

便携版当前所有持久化数据(数据库、日志、配置、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.jsonlogs/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 }"
]

升级流程:

  1. persist 项由 Scoop 创建硬链接 → 根层 flux_down.db 等指向 $persist_dir/fluxdown/ 中的真实数据
  2. 新增 portable_data persist → Scoop 创建目录 Junction → $dir/portable_data/$persist_dir/fluxdown/portable_data/(首次为空)
  3. 应用启动 → Rust/Dart 迁移代码检测根层硬链接 → renameportable_data/
  4. rename 移动硬链接本身(不拷贝数据,同 inode)→ 数据物理位置不变,目录项从 $dir/ 迁移到 $dir/portable_data/,由于后者是 Junction,实际写入 $persist_dir/fluxdown/portable_data/
  5. 根层旧硬链接路径($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/

待合并前补充

  • migrate_portable_data 单元测试(幂等性、混合文件+目录、目标已存在跳过、硬链接 rename 场景)
  • WAL 文件锁定场景验证(flux_down.db-wal 被锁 → 跳过而非崩溃)
  • Scoop manifest 两阶段更新 + 升级路径验证(0.2.1 → 0.3.0 → 0.4.0)

类型

AFK

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions