Architecture Baseline 0.5.130
本文件只保存当前有效的模块、数据和平台合同。演化记录进入
docs/history/architecture/,设计取舍进入 docs/architecture/ADR_*.md。
Presentation
-> Application / Controllers / Coordinators
-> Domain contracts and models
-> Repository / Services
-> Platform adapters
依赖只向内:Presentation 呈现状态和用户意图;Application 编排任务、取消和会话; Domain 定义标签、过滤、稳定身份和播放合同;Repository 拥有 SQLite/migration; Platform adapter 隔离文件系统、数据库 Provider、播放器、FFmpeg 和系统路径。
local roots
-> LibraryScanBackend
-> stable identity validation
-> SQLite repositories
-> TagQueryService / FilterQuery
-> visible library result
-> PlaybackSession
-> PlayerPage filtered queue
Tag Manager 写入 Repository;缩略图、媒体探测、备份和视觉取帧通过受限后台队列补充, 不得改变筛选来源或阻塞高频 UI。Rust 扫描器只枚举/提取候选元数据,不得直接写业务数据库。
合法来源:manual / folder / rule / filename / import / auto。
- folder 标签只从当前 root 文件树第一/第二层派生,二级始终属于所属一级。
- folder 可重算;manual、locked 和其它用户维护关系必须保留。
- manual 关系保持独立顶层
manual组;同名 folder/manual 不合并身份,关系优先使用tagId。 - UI、PlayerPage 和 Tag Manager 不复制查询语义,唯一所有者是
FilterQuery/TagQueryService。
same group OR
different groups AND
excluded tags NOT
keyword -> file name / path / tag name / alias
videoId: stable database identity
mediaFingerprint: file/media identity
path: mutable current location
isMissing: path unavailable while record is preserved
标签、收藏、播放记录和进度绑定 videoId。路径变化不得创建第二个用户身份或删除 manual
标签;无法确认新位置时先进入 missing。schema/migration 必须向后兼容、幂等、可回滚并保留旧数据。
schema v2 的 videos.video_id 为 PRIMARY KEY,videos.path 为可变唯一字段,Windows 维护
不区分大小写索引;video_tags(video_id, tag_id, source) 为关系主键。旧 path-keyed 数据库在同一
SQLite transaction 中换表迁移,孤立关系使迁移回滚而不静默丢失用户字段。详细取舍见
docs/architecture/ADR_003_STABLE_VIDEO_ID_AND_SCHEMA_MIGRATION.md。
VideoIdentityIndex.byVideoId 是主索引,pathKey 只是同步辅助视图;删除、重命名、missing relink
和合并删除的生产命令使用 stable-ID API。LibraryRepositoryContext 统一 connection、索引、
标签关系和事务;查询、命令、root/扫描/relink 协调分别由 Store service 拥有,不复制状态。
大库且 FTS5 可用时可生成关键词候选 SQL,dataRevision 成功写入后按需重建;候选文本包含
tag ID、名称、显示名和 alias,最终结果仍由 FilterQuery / TagQueryService 验证,小库、短词
和 FTS5 不可用时走内存路径。缩略图进程内快照按 stable videoId 索引,磁盘 key 在该
videoId 范围内优先使用 mediaFingerprint,没有 fingerprint 时才回退到 path/size/mtime,
避免 relink 后丢失可复用缓存,也避免两个数据库记录因内容相同而互相清理缓存。
FTS 候选短词按 Unicode 码点判断,别名通过 SQLite JSON 解码后写入派生索引;JSON 扩展
不可用或数据损坏时事务回滚并回退完整查询。同一 revision 的并发索引维护共享一个 Future,
跨 revision 请求等待在途事务自然结束后重新核验,失败释放句柄以允许重试。
LibraryQueryController 在候选异步返回后、更新结果缓存和调用诊断前检查请求代次、epoch
与 dispose 状态,防止旧候选污染新 revision;发布前校验继续保留。
成功空差量扫描保留进程内 searchIndexRevision,查询 dataRevision 仍推进;所有其它写入默认同时失效。
并发标签命令独立推进索引修订,扫描不得覆盖该值。反例与配对证据见 docs/qa/search_delivery_index_revision_20260905.md。
扫描零差量提交也要比较当前输入的结果 epoch,必要时重新调度最新输入;空差量复用
相同条件的结果,不额外推进页面标签 revision 或重算计数。索引聚合通过事务内
INSERT ... SELECT 写入派生 FTS,避免全库文本往返 Dart,回滚与完整查询回退保持不变。
LibraryPerformanceTrace 只在显式隔离 QA 会话启用,按请求、revision 和扫描代次记录
有界数值时序,默认关闭、不写业务文件、不持有 UI 回调。索引 SQL、扫描提交返回及查询发布
使用同一会话时钟;跨 await 关联不会跨会话复用。等待区间是应用边界墙钟,不能解释为
SQLite 内部锁等待或 CPU 时间,详见 docs/qa/query_tail_decomposition_20260905.md。
扫描活动中的进度通过页面局部 notifier 更新顶部栏,开始/结束仍刷新全页入口状态;
标签面板在一次页面构建内复用子树,宽度动画不重复创建标签内容。结果网格也只在同一次
父级数据构建内缓存可见子树,约束变化重新布局;新筛选/排序/数据快照重建缓存和卡片回调。
ResourceScheduler 除 lease 预算外提供 pending request cancellation;取消只移除尚未启动的工作,
已经取得 lease 的 FFmpeg/SQLite 工作必须自然收尾。详见 ADR_004 和
docs/architecture/ADR_005_EXTERNAL_MODULE_COMPARISON_AND_GAPS.md。
- 首屏显式区分
loading / ready / failed;失败页不暴露本机路径并复用同一应用加载边界安全重试。 - 搜索使用稳定 controller 输入链;标签点击先更新可见结果,计数和预取延后。
- 空库优先引导添加 root 以建立 folder 标签;零筛选结果必须提供清空和查看条件的恢复动作。
- 最近播放与收藏结果按当前来源惰性生成;侧栏收藏/missing 统计按媒体库 revision 单次遍历并复用。
- 收藏切换经过补偿式命令;Repository 写入失败恢复精确旧值,不能留下只存在于界面的收藏状态。
- 扫描、媒体探测、缩略图、备份和视觉复核支持取消过期任务、分阶段进度和有界并发。
- 播放器活跃时后台 FFprobe/批量取帧让渡或冻结,退出后按进入前状态恢复,不覆盖用户手动暂停。
- 可重建视觉签名保存为带算法版本、fingerprint/size/mtime 快照的 metadata,不新增 schema;失效即重算, 不进入用户备份,晚到写入必须确认 stable videoId 仍存在。
- 视觉匹配度只供人工复核;首帧 dHash 只预筛,review 组不得自动删除或移动文件。
- 目录删除、文件删除和数据库清理是不同动作,不能互相暗示授权。
用户视频删除统一经过 LibraryFileCommandExecutor 与 FileSystemAdapter.moveFileToTrash:
移入系统回收站 -> 删除 Repository 记录 -> 清理可重建缓存
删除事务同步清理标签关系、视频行、thumbnail/media_details/visual_signature metadata;相似视频 合并删除必须由用户选择保留目标,并只合并收藏和 manual 标签。启动、扫描和播放预检只能把路径 不可用的记录标记为 missing;只有用户看过数据影响并显式确认,才允许清理数据库记录与备份快照, 且清理不操作磁盘文件。临时资源使用各自清理边界。
source filtered result
-> PlaybackSession(items, currentIndex, source context)
-> PlayerService
-> PlayerBackend
- PlayerPage 只消费来源 filtered queue;右侧切换保持来源语境,返回媒体库保留筛选状态。
- Route 返回先发布已提交的 stable-ID 差量,再在尾部等待原生释放、采样和进度刷盘。
- open/stop/seek/dispose 共享媒体命令尾链;快速请求使用 generation/cancellation,旧事件不能覆盖新意图。
- 精确/交互式 seek 串行化,确认窗口屏蔽旧位置事件;播放队列、current index、进度和 UI 反馈彼此独立。
PlayerBackend是最小平台合同;可选属性、seek、诊断和媒体控制扩展不得拥有播放列表或用户数据, 不支持时必须显式安全降级,不得创建第二条解码链。
- 正式默认后端是 MediaKit Texture;Windows native mpv/child HWND 只供显式 QA,不自动成为生产默认。
PlayerService -> PlayerBackend -> MediaKit / Windows native依赖方向固定;原生窗口、线程和纹理在 Flutter dispose/退出前有序收口,排队系统消息先确认 controller 存活。- NVIDIA VSR/HDR、NVOFA、VapourSynth 和本机插件必须经过能力、许可、回退和真实性门禁,不能自动生产化。
ThumbnailService/MediaDetailsService通过FFmpegBackend/FFprobe;可见项优先、后台限流、失败可见可重试, 0-byte/不完整 JPEG 无效,diagnostics dispose 后取消 timer/异步回调,日志不得泄露媒体路径或数据库内容。 缩略图缺失补全既可由设置页明确入口启动,也会在媒体库首帧后延迟登记一次;媒体库首帧后还会错峰登记缺少媒体详情/可靠 时长的 active 视频,但不自动重试已知失败项。两类后台生产源都只按最多 500 项候选窗口推进,窗口空出后再从迭代器补入 后续项目,禁止超过窗口的候选被截断;媒体详情原生探测仍按最多 8 项小批次串行。 缩略图 cache key/JPEG 校验请求最多 24 个,处理器数达到 12 时生成并发最多 3 个,ResourceScheduler的缩略图 lease 预算最多 3 个且共享总预算仍为 4。
平台边界:FileSystemAdapter、DatabaseProvider、AppPaths、PlayerBackend、FFmpegBackend、
LibraryScanBackend。Dart core 不出现盘符、exe、Explorer/Finder 命令或打包目录假设;原生组件只通过
显式 ABI/序列化合同进入 Dart;平台不可用时返回可诊断失败或安全回退。
FileSystemAdapter.saveBytes 同时拥有保存对话框与真实字节写入:用户取消时不得创建 0-byte
占位文件,确认后由平台适配器完成写入并以 flush 复核落盘。页面不直接依赖 file_picker 的
Uri、Windows/Linux options 或联邦插件实现。
- UI 不复制过滤逻辑。
- 播放器不回退全局队列。
- folder 重算不删除 manual/locked 数据。
- mutable path 不替代 stable identity。
- 扫描和媒体工具不越过 Repository 写业务数据。
- 平台命令不进入 UI/Domain。
- 后台任务不阻塞高频交互。
- 未授权删除失败关闭;页面挂载、可达性和真实窗口行为必须有证据。
修改 schema、core、Repository/platform contract、过滤、稳定身份、播放或缓存队列时,更新本合同,
必要时增加 ADR,并运行 architecture/focused tests、flutter analyze、对应平台 build 和真实 UI 验收。