Repository Wiki
ChanIok/SpinningMomo

图库恢复机制与资产缺失生命周期

本文档覆盖 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 前端(用户触发恢复的界面)。

Loading diagram...

架构分层说明:

  • 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 覆盖基础图库行为。

生命周期与初始化设计

Loading diagram...

设计意图(WHY):

  • initialize 与 cleanup 都接受可选回调(after_ready / before_watchers_shutdown)而非让调用方轮询状态。after_ready 保证调用方在图库完全就绪(含 watcher 启动)后才执行后续动作;before_watchers_shutdown 则是一个反向钩子——因为文件系统 watcher 持有目录句柄并在回调中可能触碰 AppState,必须在 watcher 关闭之前让上层先停掉依赖它的逻辑,避免清理过程中的悬空访问。这种成对的"就绪后/关闭前"钩子是该模块生命周期安全的关键。
  • 所有函数返回 std::expected<T, std::string>,用值语义错误(错误字符串)而非异常。这让 RPC 层可以直接把错误字符串序列化给前端,同时避免在扫描这种长流程中因异常导致部分状态不一致。

核心流程:扫描驱动的资产一致性恢复

扫描是资产缺失生命周期的核心驱动力。一次扫描即一次「索引 ↔ 磁盘」的对账:

Loading diagram...

流程要点:

  1. 入口:用户在 GalleryScanDialog.vue 中确认扫描,或系统在检测到输出目录变化时自动触发(经 ensure_output_directory_media_source)。
  2. 进度回调:scan_directory 的第三个参数是 std::function<void(const ScanProgress&)> progress_callback,这是推模式进度上报——扫描在长目录上可能耗时,回调让调用方(RPC 层)能够流式转发进度到前端,避免前端只能看到最终结果。
  3. 对账结果:ScanResult 汇总本次扫描的结果(新增/更新/失效计数等,具体字段定义在 types.hpp 中,本页源码证据未展开其字段级定义)。
  4. 派生数据对齐:缩略图作为派生资产,通过 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 命名空间的完整公开接口(自由函数形式):

cpp
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::gallery

Source: 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 回调与主线程扫描之间的互斥策略定义在实现文件中,本页源码证据不足以给出结论。

测试覆盖

仓库中存在两个直接相关的端到端场景测试:

此外前端存在 GalleryDebugOverlay.vue 调试覆盖层,配合 get_thumbnail_stats 的 JSON 输出,可在真实运行时观察图库一致性状态。

相关链接

Sources

(1 files)