# 储存空间诊断与安全清理 状态:源码已实现,**尚未进行真机验收**。本文描述当前行为。数据管理页面只统计 Floe 自己的数据;App 的系统占用以 iOS“储存空间”为准,界面不会给出猜测的上限。最后更新:2026-10-16(基于实测的 APFS 克隆计量、共享快照缓存、仅统计 Floe 数据的概览与独立的“共享文件”区域)。 ## 背景 真机曾报告 Floe 的数字远高于 iOS“储存空间”。旧检查器本就在读取主机已分配大小键(`totalFileAllocatedSize`),仅用逻辑长度无法解释差异。本地探针随后证明了一个具体的重复计入机制:APFS 克隆家族的每个成员都会报告完整的已分配大小,直接累加会把同一批共享块算进多次。现在计量改为实测而不是猜测,页面也只展示能被诚实支持的数字。开发主机无法访问该用户设备,设备上的具体数值仍未确认;计量本身不涉及隐私内容,也绝不通过删除用户数据来改变结果。 ## 计量引擎 `FloeAgent/Sources/FloeCore/StorageAccounting.swift`、`StorageCloneAccounting.swift` 与 `StorageReportBuilder.swift`: - **单次互斥遍历**:重叠/嵌套根按最深匹配归属,每棵物理树只遍历一次。 - **精确身份去重**:device + inode 对硬链接和根重叠去重;被去重的字节单独报告(`sharedSize`),绝不重复累加。 - **逻辑 vs 已分配**:`logicalBytes` 是文件系统报告的表观长度(`stat` 大小),**不是** VM 磁盘的配置或已用客体容量:qcow2/容器元数据会让表观长度偏离来宾真实虚拟大小,客户机内部实际用量只能在客户机运行时测量。`allocatedBytes` 使用主机实际分配、感知稀疏的大小。两者分开保存并分别展示。 - **实测克隆计量**:用公开头文件即可调用的 `getattrlist` + `FSOPT_ATTR_CMN_EXTENDED` 读取 APFS 独占大小、克隆 ID、扩展标志与克隆引用计数。同一克隆 ID 且每个成员都带 `EF_SHARES_ALL_BLOCKS` 的纯完整克隆家族只计一次;实测独占字节只有在每个被测文件都成功回答扩展查询时才作为独占下限展示。部分写时复制的并集无法从公开 API 推导,因此绝不合成可减的“独占总量”。查询不受支持时退回按声明克隆根标记估算,并明确区分哪些数字是实测、哪些是估算。 - **隐藏文件**默认计入,不会静默漏掉暂存/`.trash`/`.downloads` 等目录。 - **竞态与错误**:扫描中消失的文件计入 `changedOrVanishedCount`,无法读取的条目计入 `errorCount`,无法测量的分配标记为不完整。不删数据、不静默忽略。 - **进度与取消**:扫描报告实测文件数并轮询 `isCancelled`;界面提供“停止扫描”,取消后保留上次有效快照。 - **不做猜测上限**:引擎不会截断、不会做启发式扣减、也不会为让页面看起来是别的数字而改写实测和;无法测量的共享会如实保留为限制。 ## 快照缓存 一个共享的 `StorageSnapshotCenter`(门面 `StorageDiagnosticService.shared`)会立即渲染上次有效数字: - 页面出现时的自动刷新有 600 秒新鲜度;下拉刷新一定重新扫描。 - 真实变更(清理完成、画布云端释放、字体或模型变化、环境变化)会使快照失效,下一次自动刷新扫描一次。 - 刷新失败保留上次有效快照并短暂冷却;取消保留上次快照;关闭页面不会终止正在填充缓存的扫描。 - 快照原子持久化(只有计数与分类,绝不含路径、文件名或内容);schema、计量版本、根清单或 App 构建不匹配时会丢弃。 ## App 接线 - 扫描覆盖 `Library`、`Documents`、`tmp` 及 Floe 各根目录(Environments/容器层与每环境稀疏磁盘、同级 `Floe/Runtime/v2`、Materials、Canvases、MediaProjects、CanvasCAD、LocalModels、PrivateTasks、Fonts、Attachments、GeneratedImages、BrowserArtifacts、Checkpoints)。`Caches`/`tmp` 以可回收文件容量展示,不列为用户数据分类。 - **概览(仅 Floe 数据)**:不显示整机总量或可用空间概览。检测到共享/可克隆数据时,概览把 **App 占用** 显示为 **以 iOS“储存空间”为准**,而不是误导性的 Floe 总量;未检测到共享的干净安装仍保留基于文件的近似估算,并附“以 iOS‘储存空间’为准”的说明。 - **共享文件**:Floe 的共享数据(虚拟机/运行时镜像与共享资源)单独成区:**共享(可克隆)文件容量**、精确去重的完整克隆字节,以及该数字是实测还是估算。这是文件容量:共享块可能被重复计入,因此不是额外的物理占用,也不是可以释放的可用空间。 - **文件容量明细**:各分类的已分配字节/项目数、可用时的实测独占下限与扫描诊断收在一层点击之内。虚拟机磁盘分类显示表观大小,并注明它不是配置的客体容量。 - 出现竞态、不可读条目或无法测量的分配时,总量会标注为不完整。 ## 安全清理 `FloeAgent/Sources/FloeCore/StorageCleanup.swift`(引擎)+ `FloeApp/Settings/StorageDiagnosticService.swift`(注册计划): - **仅显式注册候选项**:每个候选项带所有者、标题、用途、保留原因与时间阈值。当前计划只有一个候选:任务持有的专用 scratch 根 `tmp/FloeAgent/scratch`(组件 purpose 前缀过滤 + 24 小时静默期 + 逐路径租约与原子删除声明)。整个 tmp 根与 Caches 目录从来不是候选。 - **保留注册项**:Floe 的 `Caches/FloeAgent` 存放用户数据与恢复状态——提示词库(用户创作)、诊断日志与 PDF 操作日志——全部注册为*保留*并附原因,永远不会成为候选项。 - **失败关闭**:每次删除都要求所有者探针明确证明空闲(无运行环境、无模型下载、无未完成媒体任务),并在候选级与逐项删除前重新探测。未知、忙碌或探测出错时不删除任何内容。 - **跳过计数**:最近项、受保护名称、符号链接及含活跃写入子项的目录均计入跳过;清扫安全守卫会拒绝任何误注册的缓存父目录。 - **诚实结果**:报告删除/所有者忙碌跳过/受保护跳过/最近跳过/失败数量、清理根上的**已分配字节观察变化**(明确说明在克隆/稀疏卷上并非精确物理释放量),以及可测量时的卷可用空间变化。绝不把文件大小之和当作释放的卷字节。 - **可清理估算**:界面“可清理文件”使用与执行相同所有者/时间/保留规则计算,以文件容量表述;不是整目录大小,也不承诺等于释放字节。 - **取消**在项目之间生效。 ## 测试 引擎行为已由针对补丁源码运行的聚焦检查覆盖:普通文件、稀疏磁盘(逻辑 vs 已分配)、硬链接只计一次、嵌套根、parent/未归属文件、不跟随符号链接、隐藏与消失文件、取消、报告算术;纯克隆家族去重、实测独占字节、卷不支持时的退回;快照缓存重入、失效、失败冷却、持久化与存储隐私/损坏处理。App 宿主测试、完整 App 构建与真机验收仍由集成负责人执行。FloeApp 编译包含在完整 App 构建中。 ## 诚实边界 - 文件容量可能包含与克隆兄弟共享的块且重复计入,是**上限估算**,不是额外物理占用,也不是精确可释放值。 - iOS“储存空间”的统计方法未公开;Floe 不声称两者必须一致,也不会为了对齐而截断或猜测一个数字。 - “共享文件”区域与数据管理说明在中文与英文中都明确标注这一点。 - 清理绝不把候选项大小之和等同于实际释放的卷字节。