Skip to content

Latest commit

 

History

History
171 lines (127 loc) · 7.59 KB

File metadata and controls

171 lines (127 loc) · 7.59 KB

程序分发根与用户数据目录(MyPaths)

命名约定摘要见 AGENTS.md §4.2。本文是路径 API 的完整说明
app* 表示程序侧资源根(desktop:exe 同级),与安装向导 / installErrorHandlers 无关
启动编排 + 系统选目录(流程图、何时用哪套 API)见 .doc/user_data_picker.md

解决什么问题?

桌面应用常有两类文件,不应混在同一目录

类型 典型内容 xly 轨
随程序分发 tray.ico、内置 exe appappDir / appDirFile
用户业务数据 配置、日志、数据库 userDatauserDataDir / userDataDirFile

用户可能把数据放在 D:\AppData,程序在 C:\Program Files\...。库要帮你解决:

  1. 首次:让用户选定数据目录并记住
  2. 以后启动:自动恢复,不必每次再问。

MyPaths 只负责:根目录已定之后怎么读写文件。
「根从哪来、怎么持久化、要不要弹系统选夹」→ MyUserDataDirStore + MyUserDataDirSession +(可选).doc/user_data_picker.md 里的 MyPicker

两层目录(Bootstrap 指针 vs 真实数据)

flowchart LR
  subgraph appSupport [系统 AppSupport]
    JSON["user_data_dir.json\n字段 userDataDir"]
  end
  subgraph userDisk [用户磁盘]
    DATA["D:/MyAppData\nconfig.json / logs/ ..."]
  end
  Store["MyUserDataDirStore\nload / save"] --> JSON
  MyPaths["MyPaths\nsetUserDataDir"] --> DATA
  JSON -.->|"指针:记住路径"| DATA
Loading
  • Bootstrap JSON:小文件,只存「用户数据根在哪」;默认在 getApplicationSupportDirectory() 下。
  • 真实数据目录:用户选的(或移动端 Documents);userDataDirFile('config.json') 都相对这里。

命名:Dir 与 DirFile

后缀 返回 含义
Dir String(目录路径) 轨根或固定子目录(如 appDiruserDataLogsDir
DirFile Future<File> 对应 *Dir 根下的相对路径文件(如 appDirFileuserDataDirFile
…To…Dir Future<File> copyAssetToAppDir:复制到某轨根,返回值仍是文件

需要文件路径字符串:(await MyPaths.userDataDirFile('a.json')).path

不提供 *FilePath() 公开方法。Web 目标请 import 'package:xly/paths.dart':自动选用 MyPaths 桩实现(全部 API 抛 UnsupportedError);MyUserDataDirStore 等仍依赖 dart:io,Web 勿 import。

两条轨

API 前缀 用途
app appDir / appDirFile / copyAssetToAppDir exe 旁资源、托盘图标、从 assets 落到程序侧
userData setUserDataDir / userDataDir / userDataDirFile / … 配置、日志、业务 JSON
  • 便携应用:只用 app*
  • 桌面、数据与 exe 分离setUserDataDir + userData*;可选 MyUserDataDirStore 记住目录(Bootstrap 指针,在 AppSupport 下 JSON)。

appDir 跨平台

  • 桌面:exe 所在目录(同步)。
  • 移动:Documents 作可写资源 fallback(无 exe 旁「安装目录」语义,行为等同旧 getAppDirectory 移动侧)。
  • WebUnsupportedError

公开 API(MyPaths)

API 返回 说明
appDir String app 轨根目录
appDirFile(relativePath, {androidPreferExternal}) Future<File> app 轨下文件
setUserDataDir(path, {clearCache}) void 设置 userData 根
userDataDir String 已设置的根;未设置抛 StateError
isUserDataDirSet bool 是否已设置
userDataDirFile(relativePath) Future<File> userData 轨下文件
userDataLogsDir() Future<String> logs/ 子目录
copyAssetToAppDir / copyAssetToUserDataDir Future<File> assets/ 复制(目标不存在时写入)
atomicWriteString(file, content) Future<void> 原子写

relativePath:可为 'config.json''logs/app.log';禁止 .. 与绝对路径。

关联类型(paths 子入口)

类型 职责
MyUserDataDirStore Bootstrap 指针:AppSupport 下 JSON,在应用调用 save 时写入
MyUserDataDirValidator 目录存在/可写评估与风险提示(无 UI)
MyUserDataDirSession prepare / apply 启动编排(BootstrapResultstoredPath / storedEvaluationapply 可选 onAfterApply

默认 bootstrap:user_data_dir.json、字段 userDataDir(构造参数可覆盖)。

Session.prepare 在启动时做什么?

flowchart TD
  start([应用启动]) --> load[Store.load 读 bootstrap 指针]
  load --> valid{路径存在且可写?}
  valid -->|是| set[setUserDataDir]
  set --> ok([loadedPath 有值\n可直接 userDataDirFile])
  valid -->|否 / 无记录| desktop{桌面且需自选目录?}
  desktop -->|是| setup([needsDesktopSetup = true\n需 UI 或 MyPicker])
  desktop -->|否 如移动无 Store| doc[Documents + apply]
  doc --> ok
Loading

prepare 不会弹出系统选夹;它只告诉你:内存里的 userData 根是否已经定好。定不好时,再用 picker 文档 里的 MyPicker.dir / 自研设置页 + Session.apply

何时需要哪套能力?

你的应用 需要 Store / Session / Picker?
便携版,数据跟 exe 走 ,只用 app*
桌面,数据与 exe 分离,要记住用户选择 ,推荐 prepare + 必要时 MyPicker
移动,固定 Documents 即可 可用 prepare(无 Store 时自动 apply Documents),或自行 setUserDataDir
Web 路径 API 不可用(UnsupportedError
时刻 用什么 解决什么
每次启动 MyUserDataDirSession.prepare 自动恢复 userData 根;返回是否要再选目录
首次 / Store 失效 prepare 之后 → MyPicker.dir 或自研 Dialog → apply 用户选定目录并写入 bootstrap
菜单「更改数据目录」 MyPicker.userDataDirAndApply 系统选夹 + 校验 + 写 Store(见 picker 文档)
设置页手输路径 Session.apply 已有字符串,不弹 OS 对话框
根已设,日常读写 MyPaths.userDataDirFile 与 Store/Session 无关

日常用法

推荐(启动编排;未配置时见 picker 文档 补选目录):

final store = MyUserDataDirStore.defaultInstance;
final boot = await MyUserDataDirSession.prepare(store: store);

if (boot.needsDesktopSetup) {
  // MyPicker.dir() 或自研设置对话框 → Session.apply(...)
} else {
  final ico = await MyPaths.appDirFile('tray.ico');
  final config = await MyPaths.userDataDirFile('config.json');
}

进阶 · 手动三步(完全自己控制顺序时再用;Store.load() 返回 String?,即 bootstrap JSON 里保存的用户数据目录路径,无记录为 null):

final ico = await MyPaths.appDirFile('tray.ico');

final saved = await store.load();
MyPaths.setUserDataDir(saved ?? userPickedDir);

final config = await MyPaths.userDataDirFile('config.json');

自定义 Bootstrap 文件名

const MyUserDataDirStore(
  bootstrapFileName: 'my_app_data_pointer.json',
  jsonPathKey: 'dataRoot',
);

端到端流程与代码示例见 .doc/user_data_picker.md

测试

flutter test test/my_paths_test.dart