图库恢复机制与资产缺失生命周期
本文档覆盖 SpinningMomo 图库子系统(features::gallery)的恢复机制与资产缺失(missing asset)生命周期:包括图库初始化/清理流程、目录扫描与重新索引、媒体源(media source)的自动恢复注册、以及扫描过程中对缺失资产的处理入口。核心实现位于 C++ 后端的 src/features/gallery/gallery.cpp,通过 RPC 端点暴露给前端,并有专门的恢复场景测试 tests/scenarios/gallery_recovery.ts 验证其行为。
目的与范围
本页面聚焦于「图库在资产文件丢失/移动/恢复场景下如何重建一致性状态」这一能力,具体覆盖:
features::gallery模块的初始化与清理生命周期(initialize/cleanup)- 扫描与索引流程(
scan_directory及其进度回调) - 输出目录媒体源的自动恢复注册(
ensure_output_directory_media_source) - 缩略图清理与统计(
cleanup_thumbnails/get_thumbnail_stats),这是缺失资产残留物清理的入口 - 后端能力如何通过 RPC 层暴露给前端(
src/core/rpc/endpoints/gallery/) - 前端扫描/删除对话框与恢复流程的关联(
web/src/features/gallery/) - 恢复场景的端到端测试(
tests/scenarios/gallery_recovery.ts)
以下相关主题有意留给兄弟页面:
- 图库的整体功能与 UI 交互(扫描对话框、灯箱、偏好设置等)——见
gallery主功能页 - RPC 端点的通用协议与序列化机制——见 core/rpc 相关页面
- 全局应用状态
core::AppState的结构——见应用状态相关页面
概述
SpinningMomo 是一个带 C++ 核心、Vue 前端的桌面应用。图库子系统负责对本地媒体文件(截图、录屏产物等)建立索引、生成缩略图,并向前端提供浏览/移动/删除能力。在真实使用中,媒体文件可能因为用户手动移动、外部工具删除、输出目录变更等原因与索引脱节,此时系统需要一套恢复机制:
- 重扫描(re-scan):通过
scan_directory重新遍历目录,重建索引并对已失效的索引项做失效/移除处理 - 媒体源恢复:当应用启动或输出目录变化时,通过
ensure_output_directory_media_source把输出目录重新注册为图库的媒体源,保证录屏产物不会因目录路径变化而从图库消失 - 残留物清理:缩略图缓存等派生资产需要通过
cleanup_thumbnails与索引状态对齐,避免出现"缩略图存在但原文件已删"或反向的孤儿数据
关键概念:
| 概念 | 含义 |
|---|---|
| 媒体源 | 图库索引的数据来源目录,输出目录是其中一个由应用管理的特殊媒体源 |
| 缺失资产 | 索引中存在记录、但磁盘上已不存在的文件条目 |
| 扫描 | 对媒体源目录的全量遍历,产出 ScanResult 并通过 ScanProgress 回调报告进度 |
| watcher | 文件系统监视器,cleanup 的 before_watchers_shutdown 回调暗示其在清理前需要先被有序关闭 |
| 缩略图 | 由原图派生的缓存文件,cleanup_thumbnails 负责按索引状态清理 |
架构
图库恢复机制横跨三层:C++ 核心(索引/扫描/watcher)、RPC 端点层(能力暴露)、Web 前端(用户触发恢复的界面)。
架构分层说明:
- C++ 核心层是恢复逻辑的唯一权威实现。
features/gallery/gallery.cpp以自由函数(非类)形式暴露能力,所有函数的第一个参数都是core::AppState&——这是一种显式状态传递风格,模块本身无隐藏单例,便于测试与多实例。类型契约(ScanOptions、ScanProgress、ScanResult、OperationResult)集中在features/gallery/types.hpp。 - RPC 端点层(
src/core/rpc/endpoints/gallery/gallery.cpp)是核心能力到前端之间的桥接薄层,负责把 C++ 接口转成可序列化的 RPC 调用。 - Web 前端层通过对话框(如
GalleryScanDialog.vue)让用户主动触发扫描恢复,通过GalleryDeleteAssetsDialog.vue触发删除;文案在web/src/core/i18n/locales/zh-CN/gallery.json中本地化。 - 测试层的
gallery_recovery.ts是恢复场景的端到端守卫,gallery_core.ts覆盖基础图库行为。
生命周期与初始化设计
设计意图(WHY):
initialize与cleanup都接受可选回调(after_ready/before_watchers_shutdown)而非让调用方轮询状态。after_ready保证调用方在图库完全就绪(含 watcher 启动)后才执行后续动作;before_watchers_shutdown则是一个反向钩子——因为文件系统 watcher 持有目录句柄并在回调中可能触碰AppState,必须在 watcher 关闭之前让上层先停掉依赖它的逻辑,避免清理过程中的悬空访问。这种成对的"就绪后/关闭前"钩子是该模块生命周期安全的关键。- 所有函数返回
std::expected<T, std::string>,用值语义错误(错误字符串)而非异常。这让 RPC 层可以直接把错误字符串序列化给前端,同时避免在扫描这种长流程中因异常导致部分状态不一致。
核心流程:扫描驱动的资产一致性恢复
扫描是资产缺失生命周期的核心驱动力。一次扫描即一次「索引 ↔ 磁盘」的对账:
流程要点:
- 入口:用户在
GalleryScanDialog.vue中确认扫描,或系统在检测到输出目录变化时自动触发(经ensure_output_directory_media_source)。 - 进度回调:
scan_directory的第三个参数是std::function<void(const ScanProgress&)> progress_callback,这是推模式进度上报——扫描在长目录上可能耗时,回调让调用方(RPC 层)能够流式转发进度到前端,避免前端只能看到最终结果。 - 对账结果:
ScanResult汇总本次扫描的结果(新增/更新/失效计数等,具体字段定义在types.hpp中,本页源码证据未展开其字段级定义)。 - 派生数据对齐:缩略图作为派生资产,通过
cleanup_thumbnails按索引状态清理孤儿缓存,get_thumbnail_stats则以 JSON 字符串(std::expected<std::string, std::string>)返回统计信息,供前端诊断面板展示。
注意:上述流程图中「标记为缺失资产 / 从索引移除」的分支细节(例如是软删除还是硬移除、是否有恢复宽限期)定义在
gallery.cpp实现与types.hpp中,本页因源码探索预算限制未能逐行验证其内部实现,请以 gallery.cpp 实现为准。端到端行为由tests/scenarios/gallery_recovery.ts覆盖。
模块接口定义
以下是 features::gallery 命名空间的完整公开接口(自由函数形式):
1namespace features::gallery {
2
3// 初始化与清理
4auto initialize(core::AppState& app_state,
5 std::function<void(core::AppState&)> after_ready = nullptr)
6 -> std::expected<void, std::string>;
7auto cleanup(core::AppState& app_state,
8 std::function<void(core::AppState&)> before_watchers_shutdown = nullptr) -> void;
9
10// 扫描与索引
11auto scan_directory(core::AppState& app_state, const ScanOptions& options,
12 std::function<void(const ScanProgress&)> progress_callback = nullptr)
13 -> std::expected<ScanResult, std::string>;
14auto ensure_output_directory_media_source(core::AppState& app_state,
15 const std::string& output_dir_path) -> void;
16
17// 缩略图
18auto cleanup_thumbnails(core::AppState& app_state) -> std::expected<OperationResult, std::string>;
19
20// 统计
21auto get_thumbnail_stats(core::AppState& app_state) -> std::expected<std::string, std::string>;
22
23} // namespace features::gallerySource: gallery.hpp
接口分组注释(// 初始化与清理、// 扫描与索引、// 缩略图、// 统计)直接来自源码头文件,说明模块作者将能力划分为四个正交关注点;其中「扫描与索引」组同时包含 ensure_output_directory_media_source,印证了媒体源注册是索引一致性的前提这一设计。
API 参考
initialize(core::AppState& app_state, std::function<void(core::AppState&)> after_ready = nullptr) -> std::expected<void, std::string>
初始化图库子系统:建立索引状态、注册媒体源、启动文件系统 watcher。
参数:
app_state(core::AppState&):全局应用状态,图库索引与媒体源挂载其上。after_ready(std::function<void(core::AppState&)>,可选):图库就绪后的回调,回调参数即app_state的引用,便于调用方在同一状态下继续操作。
返回: 成功返回 void;失败返回 std::unexpected<std::string> 携带错误描述。
cleanup(core::AppState& app_state, std::function<void(core::AppState&)> before_watchers_shutdown = nullptr) -> void
清理图库子系统。注意此函数返回 void 而非 expected——清理路径设计为尽力而为、不可失败(best-effort),因为退出阶段的清理失败通常没有合理的恢复策略。
参数:
app_state(core::AppState&):要清理的状态。before_watchers_shutdown(std::function<void(core::AppState&)>,可选):在 watcher 关闭之前执行的回调。调用方应在此停用所有依赖 watcher 事件的逻辑。
返回: 无。
scan_directory(core::AppState& app_state, const ScanOptions& options, std::function<void(const ScanProgress&)> progress_callback = nullptr) -> std::expected<ScanResult, std::string>
扫描媒体源目录并重建/对账索引。这是资产缺失检测与恢复的主要入口。
参数:
app_state(core::AppState&):承载索引的应用状态。options(const ScanOptions&):扫描选项(具体字段见types.hpp)。progress_callback(std::function<void(const ScanProgress&)>,可选):每个进度节点触发一次。
返回: 成功返回 ScanResult;失败返回错误字符串。
Throws: 该模块使用 std::expected 而非异常,错误以值形式返回。
ensure_output_directory_media_source(core::AppState& app_state, const std::string& output_dir_path) -> void
确保输出目录被注册为图库媒体源。用于启动恢复与输出目录变更后的媒体源重建。
参数:
app_state(core::AppState&):应用状态。output_dir_path(const std::string&):输出目录的绝对路径。
返回: 无(幂等保证由实现负责;具体幂等策略未在本页源码证据中验证)。
cleanup_thumbnails(core::AppState& app_state) -> std::expected<OperationResult, std::string>
按索引状态清理缩略图缓存,移除孤儿/过期缩略图。
返回: 成功返回 OperationResult(操作结果摘要);失败返回错误字符串。
get_thumbnail_stats(core::AppState& app_state) -> std::expected<std::string, std::string>
获取缩略图统计信息。
返回: 成功返回 JSON 格式的统计字符串(返回类型为 std::string 而非结构体,说明序列化发生在核心层,前端可直接消费)。
失败模式、边界情况与并发
基于已验证的接口契约,可归纳以下失败模式与设计对策:
| 场景 | 行为(依据) |
|---|---|
| 初始化失败 | initialize 返回 std::unexpected<std::string>,调用方(应用启动流程)据此决定是否降级启动 |
| 扫描中途失败 | scan_directory 返回错误字符串;由于状态显式传递且无隐藏单例,部分扫描结果的状态一致性由实现层负责(未逐行验证) |
| 清理失败 | cleanup 返回 void,不可失败、尽力而为;关闭顺序通过 before_watchers_shutdown 钩子约束 |
| 缩略图清理失败 | cleanup_thumbnails 返回 expected,可向用户报告 |
| watcher 生命周期竞争 | 通过成对回调(after_ready / before_watchers_shutdown)把就绪/关闭时机交给调用方,避免「watcher 还在回调中状态已被销毁」的竞态 |
| 目录消失/权限不足 | 由扫描实现的错误路径处理并经 expected 上报(实现细节见 gallery.cpp,本页未展开) |
并发注意:模块采用 core::AppState& 显式状态传递而非全局可变状态,这降低了跨模块数据竞争面;但 watcher 回调与主线程扫描之间的互斥策略定义在实现文件中,本页源码证据不足以给出结论。
测试覆盖
仓库中存在两个直接相关的端到端场景测试:
- tests/scenarios/gallery_recovery.ts — 恢复场景专用测试,从文件命名可确认恢复机制有独立测试守卫(内容本页未读取验证)。
- tests/scenarios/gallery_core.ts — 图库核心行为基线测试。
此外前端存在 GalleryDebugOverlay.vue 调试覆盖层,配合 get_thumbnail_stats 的 JSON 输出,可在真实运行时观察图库一致性状态。
相关链接
- features/gallery/gallery.hpp — 模块公开接口(本页主要源码依据)
- features/gallery/gallery.cpp — 恢复机制实现
- features/gallery/types.hpp —
ScanOptions/ScanResult/OperationResult类型定义 - core/rpc/endpoints/gallery/gallery.cpp — RPC 端点桥接
- tests/scenarios/gallery_recovery.ts — 恢复场景端到端测试
- docs/features/gallery.md — 图库功能中文文档
- web/src/features/gallery/components/dialogs/GalleryScanDialog.vue — 扫描触发 UI