Skip to content

Feat/snapshot resources - #1104

Open
3316891527 wants to merge 6 commits into
AAswordman:devfrom
3316891527:feat/snapshot-resources
Open

Feat/snapshot resources#1104
3316891527 wants to merge 6 commits into
AAswordman:devfrom
3316891527:feat/snapshot-resources

Conversation

@3316891527

@3316891527 3316891527 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

变更说明 / Description

本 PR 改进原始快照备份中的主题媒体资源管理,包括当前引用资源收集、资源目录整理、历史媒体排除,以及日志导出控制。

背景与动机 / Context and motivation

原始快照会把 files 目录中的历史头像、背景图和字体一并打包,导致快照体积较大(本人实际体验中,391MB大小的快照文件中有131MB是字体和图片资源,大约33.5%的空间占用全都是无效资源)。同时,当前仍在使用的主题媒体资源与普通应用文件混在一起,难以人工检查和迁移。

本次改动将当前引用的主题媒体资源单独保存到快照中的可读目录,并排除明确属于历史遗留且未被当前配置引用的主题媒体文件。

改动范围 / Changes

  • 新增主题媒体资源引用收集逻辑,支持:
    • 角色卡用户头像
    • 角色卡 AI 头像
    • 背景图
    • 用户气泡图片
    • AI 气泡图片
    • 主字体
    • 用户气泡字体
    • AI 气泡字体
    • 全局用户头像
  • 显式收集内置 default_character 的主题资源。
  • 当前引用资源写入以下快照目录:
    • payload/resources/character_cards/<角色名_角色ID>/
    • payload/resources/character_groups/<群组名_群组ID>/
    • payload/resources/global/
  • 在导出原始 files 目录时,跳过已经单独写入 payload/resources/ 的当前引用媒体,避免重复打包。
  • 导出时排除 files 根目录下明确匹配主题媒体命名规则、且未被当前配置引用的历史图片和字体文件。
  • 未引用媒体只从导出快照中排除,不删除设备上的本地文件。
  • 保留日志导出功能,并增加日志导出开关:
    • files/logs/
    • external_files/anr_reports/
  • 日志开关关闭时只影响快照内容,不删除设备上的日志文件。
  • 对快照 manifest 增加资源映射和日志导出状态字段。
  • 增加资源路径安全校验和快照资源目录路径解析逻辑。
  • 该pr改动仅用于存储有效的媒体文件,未明确实现自动应用有效的图片/字体等资源的效果。

兼容性与风险 / Compatibility and risks

  • 旧版 manifest 中没有资源路径元数据时,继续兼容解析原始 file: URI。
  • 资源文件只允许恢复到应用数据目录或应用外部文件目录范围内。
  • content:// 等外部授权 URI 不属于本次资源文件路径恢复范围。
  • 快照导出会减少未引用主题媒体占用的空间,但不会清理设备本地历史媒体。
  • 原始快照恢复会覆盖应用数据目录,使用前应先备份当前数据。
  • 当前改动涉及原始快照的文件筛选、资源映射和日志目录筛选,建议重点检查导出内容和恢复过程。

关联 Issue / Related issue

暂无。

验证方式 / Verification

检查或命令:
git diff --check

环境与变体:
本地 Android 编译环境缺少已接受许可的 Android SDK/NDK,未完成本地 Gradle 编译。
GitHub Actions Android Build:
- run 33890578501:失败,原因是日志目录常量重命名后的旧引用未同步更新。
- run 33893056137:针对该编译问题重新触发的 Android Build,成功通过编译及本机测试。

结果:
git diff --check 通过。
最新提供的原始快照已确认:
- manifest.resources 包含 3 条资源映射;
- payload/resources/character_cards/ 下存在用户头像、AI 头像和背景图明文文件;
- 导出端资源收集和资源目录写入已生效。

证据 / Evidence

已验证的快照文件:

----text-----
operit_raw_snapshot_2026-09-04_22-25-52.zip

快照中存在:

----text-----
payload/resources/character_cards/Echo_lt_character/user_avatar_Echo_haracter.png
payload/resources/character_cards/Echo_lt_character/ai_avatar_Echo_haracter.png
payload/resources/character_cards/Echo_lt_character/background_Echo_haracter.jpg

manifest 中对应的资源记录包括:

----json-----
{
  "ownerType": "CHARACTER_CARD",
  "kind": "USER_AVATAR",
  "ownerId": "default_character",
  "ownerName": "Echo"
}

----json-----
{
  "ownerType": "CHARACTER_CARD",
  "kind": "AI_AVATAR",
  "ownerId": "default_character",
  "ownerName": "Echo"
}

----json-----
{
  "ownerType": "CHARACTER_CARD",
  "kind": "BACKGROUND",
  "ownerId": "default_character",
  "ownerName": "Echo"
}

… rebinding

- Collect currently referenced avatar/background/bubble/font resources for all
  character cards, character groups and the global user avatar.
- Copy only referenced files into payload/resources/ (owner dirs + readable names),
  record originalUri/snapshotPath mapping in manifest.resources.
- Skip re-copying referenced source files in raw files/external_files scan.
- On restore, rebind each snapshot resource back to its original app-private path.
- Keep includeTerminalData/databases/shared_prefs/datastore behaviour unchanged.
Theme media (user/ai/group avatars, background, bubble images and custom
fonts) are persisted as flat files in filesDir root with known prefixes.
Only files currently referenced by a character card/group or the global
user avatar are kept (they live once under payload/resources); any other
file matching those prefixes is now excluded from the raw files copy so
historical media no longer bloats the snapshot. Logs and non-theme data
are intentionally left untouched.
The built-in default_character persona is not returned by getAllCharacterCards(),
so its background / user avatar / ai avatar (stored under
character_card_theme_default_character_* keys) were never collected. They were
therefore treated as unreferenced history and pruned, leaving the exported
snapshot without the current main-chat resources. Collect the default scope
explicitly so those files are kept under payload/resources and restored.
@CATMIAOZHI
CATMIAOZHI self-requested a review September 7, 2026 00:27

@CATMIAOZHI CATMIAOZHI left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BLOCK:3 项 P2,触发条件、后果和建议见行内评论。

审计提交:fb264c39d6c4cb96791d30820c20e18b2d3071b4,目标分支 dev。已先请求 CATMIAOZHI review,再完成独立工作树全 diff、运行调用链和独立 agent 复核;git diff --check 通过。

验证范围:上游仓库该 HEAD 的 check-runs 为 0,审计前无 review 线程或普通评论。已核对工作流内容:PR workflow 仅匹配 development,本 PR 目标为 dev;构建/单测 push workflow 匹配 main。已核对 Actions Variables(GITEE_OWNER、GITEE_REPO)。PR 引用的 Android Build 确实成功,但提交是 2b71f5f,与本次 HEAD 不同;未获得当前 HEAD 的构建或单测成功证据。遵守目标仓库 AGENTS.md,本次未执行本地编译、测试、Lint 或真机验证,未修改源码。

备份与恢复仅做静态调用链核查,未执行任何真实数据导出、覆盖、恢复或清理操作。

snapshot: ThemePreferenceSnapshot,
owner: RawSnapshotResourceOwner,
) {
if (snapshot.useBackgroundImage) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 保留已保存但暂时关闭的主题媒体引用

选择背景或字体后关闭显示开关,设置页只改布尔值,仍保存对应 URI,用户随后可以直接重新启用。但这里因开关关闭不收集文件,RawSnapshotBackupManager 又将剩余匹配前缀的媒体从原始 files 备份中排除。恢复快照时 replaceDirContents 会移除原文件,重新启用背景/字体便找不到资源。请按保存的 URI 收集引用;显示开关关闭不等于资源已不再被配置引用。气泡图片和字体的同类判断也需一致处理。

RawSnapshotResourceKind.AI_AVATAR,
userPreferences.getAiAvatarForCharacterGroupFlow(group.id).first(),
)
addThemeReferences(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 群组也需要收集自定义用户头像

群组主题使用同一编辑器,可以保存 custom_user_avatar_uri,聊天气泡也实际优先显示该头像。这里群组只收 AI 头像和 addThemeReferences,后者没有用户头像;customUserAvatarUri 仅在角色卡路径收集。群组独有的 user_avatar_* 文件因此会被后续未引用媒体筛选排除,备份恢复后丢失。请从群组的 ThemePreferenceSnapshot 同样收集 customUserAvatarUri。

val sharedPrefsDir = File(dataDir, "shared_prefs")
val datastoreDir = File(dataDir, "datastore")
val databasesDir = File(dataDir, "databases")
val resourceReferences = DefaultRawSnapshotResourceReferenceProvider(context).collectReferences()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 保留不依赖偏好解析的原始救援导出能力

数据救援页也调用此导出函数。现在必须先完成 collectReferences,其中直接 first() 读取 display DataStore、角色卡和主题配置;当 display_preferences 损坏或配置 schema 高于当前版本时,这些读取会抛异常并直接终止导出,救援页只得到失败而没有原始快照。旧路径仅复制原始文件,还明确容忍数据库 checkpoint 失败,能保全无法解析的数据。请让救援原始导出独立于成功解析偏好,避免资源整理阻断故障数据归档。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants