
Spacedrive Entry 中心化数据模型用统一 Entry 建模文件、目录与符号链接的 VDFS 核心设计【免费下载链接】spacedriveSpacedrive is an open source cross-platform file explorer, powered by a virtual distributed filesystem written in Rust.项目地址: https://gitcode.com/gh_mirrors/sp/spacedriveSpacedrive 的虚拟分布式文件系统VDFS以Entry作为一切文件系统对象文件、目录、符号链接的统一载体无论对象种类如何Entry通过metadata_id外键链接一条UserMetadata记录使任意文件在索引发现的第一时间就具备打标签、加备注、收藏等元数据能力。本文以任务文档 CORE-001-entry-centric-model.md 为核心骨架结合core工作区中的领域模型、数据库实体、迁移脚本与同步实现完整讲解 Entry 模型的字段设计、数据库 Schema、内容身份去重机制与跨设备同步语义帮助读者理解并复用这套“文件即 Entry”的数据建模思路。一、从任务到落地CORE-001 的交付物与仓库路径映射CORE-001 是 Spacedrive core 任务看板中的一项已交付任务status: Done负责人jamiepine父任务CORE-000优先级 High其目标非常明确实现通用的Entry数据模型它表示任意文件系统条目文件、目录、符号链接。每个Entry都通过一条链接的UserMetadata记录具备即时元数据能力允许用户在文件被发现的瞬间就对其进行打标签与组织。任务文档给出的三条实现要点与验收标准如下原文摘录核心领域模型定义于src/domain/entry.rs对应数据库实体实现于src/infrastructure/database/entities/entry.rsEntryKind枚举正确区分File、Directory、Symlink三种类型验收标准均已勾选完成Entry结构体存在且包含metadata_id与content_id字段系统能使用统一模型表示文件与目录数据库 Schema 正确反映Entry模型。需要说明的是任务文档撰写时使用的路径是简写形式在当前仓库的实际布局中这些模块的落点为任务文档中的路径仓库中的实际实现src/domain/entry.rscore/src/domain/file.rsEntryKind与聚合模型Filesrc/infrastructure/database/entities/entry.rscore/src/infra/db/entities/entry.rsSeaORM 实体EntryKind枚举领域层与数据库层各有一份数值编码一致见下文此外领域模型通过 core/src/domain/mod.rs 统一导出pub use file::{EntryKind, File, Sidecar};与pub use user_metadata::UserMetadata;供上层 ops、service 与前端 SDK 直接引用。二、Entry 统一模型一个模型表达文件、目录与符号链接2.1 领域层的EntryKind枚举在 core/src/domain/file.rs 中领域层定义了三值枚举并通过serde与specta::Type派生使其既能序列化传输也能自动生成 TypeScript 类型供前端使用/// Type of filesystem entry #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Type)] pub enum EntryKind { /// Regular file File, /// Directory Directory, /// Symbolic link Symlink, }数据库实体层在 core/src/infra/db/entities/entry.rs 中定义了数值编码完全一致的持久化枚举并实现i32与枚举的双向转换任何未知值都安全回退为File#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum EntryKind { File 0, Directory 1, Symlink 2, }领域层在将数据库模型转换为File时也按同一映射处理见File::from_entity_modelcore/src/domain/file.rs0 → File、1 → Directory、2 → Symlink。目录、文件、符号链接从此共享同一张表、同一套索引、同一套同步通道而无需为每种对象维护独立模型——这正是“Entry 中心化”的核心收益。2.2 聚合模型FileEntry 的“开发者友好面”Entry只是持久化骨架面向业务与前端的是聚合模型Filecore/src/domain/file.rs。它是计算型领域模型不重复存储数据而是把Entry、ContentIdentity、Tag、Sidecar、媒体元数据在查询时一次性组装pub struct File { pub id: Uuid, pub sd_path: SdPath, // VDFS 通用路径 pub kind: EntryKind, // 文件 / 目录 / 符号链接 pub name: String, pub extension: OptionString, // 不含点号 pub size: u64, pub content_identity: OptionContentIdentity, // 内容哈希身份用于去重 pub alternate_paths: VecSdPath, // 共享同一内容的其它路径副本 pub tags: VecTag, // 语义标签 pub sidecars: VecSidecar, // 侧车文件 pub image_media_data: OptionImageMediaData, pub video_media_data: OptionVideoMediaData, pub audio_media_data: OptionAudioMediaData, pub created_at: DateTimeUtc, pub modified_at: DateTimeUtc, pub accessed_at: OptionDateTimeUtc, // ... }File实现Identifiabletraitcore/src/domain/file.rs声明了同步依赖链entry、content_identity、sidecar、image_media_data、video_media_data、audio_media_data、user_metadata、user_metadata_tag、tag。这意味着当任一条依赖例如标签发生变化时资源系统可以沿依赖图反查受影响的文件并重发事件。其中与 Entry 直接相关的路由模式有直接映射entry依赖下File ID Entry UUID经内容身份扇出content_identity依赖下先按 UUID 找到内容身份再查出所有content_id指向它的Entry——一条内容身份可对应多个物理位置经用户元数据扇出user_metadata/user_metadata_tag依赖下依据元数据的作用域entry 级或 content 级路由到单个 Entry 或该内容的所有 Entry。批量组装由File::from_entry_uuidscore/src/domain/file.rs完成通过is_in批量加载 entries、content_identities、sidecars、tags并借助HashMap归组避免 N1 查询同时为每个文件填充alternate_paths含自身物理路径与其它同内容副本使前端可以据此实现“服务器端过滤”与去重展示。三、即时元数据能力metadata_id与常驻UserMetadata3.1 设计动机文件被发现的瞬间即可组织任务文档强调“EveryEntryis designed for immediate metadata capability via a linkedUserMetadatarecord”。领域层对这一点有更直白的注释core/src/domain/user_metadata.rsThis is the key innovation: EVERY Entry has UserMetadata, even if empty. This means any file can be organized immediately without content indexing.即即使某个文件尚未完成内容指纹/哈希content_id为空它也能立刻被收藏、加备注、隐藏或打标签因为元数据能力不依赖内容识别结果。3.2 领域结构UserMetadatacore/src/domain/user_metadata.rs 定义的UserMetadata包含字段类型说明idUuid唯一标识与Entry.metadata_id对应notesOptionString自由文本备注favoritebool是否收藏hiddenbool是否隐藏custom_fieldsJsonValue自定义扩展字段未来扩展点created_at/updated_atDateTimeUtc创建与更新时间UserMetadata::new(id)生成全空元数据notes: None、favorite: false、hidden: false、空对象custom_fields并提供set_notes、toggle_favorite、set_hidden与is_empty()等操作每个变更都会刷新updated_at。单元测试test_empty_metadata验证了空元数据的判定逻辑core/src/domain/user_metadata.rs。3.3 数据库实体作用域Scope设计数据库层的user_metadata实体core/src/infra/db/entities/user_metadata.rs将作用域建模为二选一的可空外键pub entry_uuid: OptionUuid, // File-specific metadata (higher priority) pub content_identity_uuid: OptionUuid, // Content-universal metadata (lower priority)MetadataScope枚举同文件 L68-L72明确两种语义Entry 级MetadataScope::Entry只作用于某一个 Entry 实例如“这个副本的备注”Content 级MetadataScope::Content作用于拥有同一内容身份的所有 Entry如“这张照片的收藏无论在哪台设备上”——File::from_entry_uuids的标签组装逻辑core/src/domain/file.rs会把 content 级标签自动应用到该内容的所有副本上。创建与获取由UserMetadataManagercore/src/ops/metadata/manager.rs提供get_or_create_entry_metadata(entry_uuid)L47-L79与get_or_create_content_metadata(content_identity_uuid)L82 起。以 entry 级为例其创建逻辑为先按entry_uuid查询不存在则插入一条notesNone, favoritefalse, hiddenfalse, custom_data{}的空元数据记录从而兑现“发现即可用”。四、内容身份content_id与跨设备去重Entry的另一个关键外键是content_id指向内容身份表用于去重多个物理 Entry不同设备、不同路径可以共享同一个内容身份。领域模型ContentIdentitycore/src/domain/content_identity.rs包含uuid、kindContentKind、content_hash、integrity_hash、mime_type_id、text_content、total_size、entry_count、first_seen_at、last_verified_at。ContentKind是 27 个值的分类枚举Unknown0、Image1、Video2、Audio3、Document4、Archive5……Memory26见 core/src/domain/content_identity.rs。哈希生成策略ContentHashGenerator同文件 L132-L264值得一提因为它直接决定去重的成本与准确性空文件直接拒绝生成内容身份ContentHashError::EmptyFile避免所有空文件因相同哈希被误判为同一内容小于等于MINIMUM_FILE_SIZE100KB1024 * 100的文件整读全哈希大文件采用采样哈希读头部 8KBHEADER_OR_FOOTER_SIZE 均匀分布的 4 个采样块SAMPLE_COUNT4每块 10KBSAMPLE_SIZE 尾部 8KB总共只传输约 58KB 数据。该算法通过VolumeBackend::read_range实现因此对云端文件同样只需范围读即可高效计算内容身份无需整文件下载。五、数据库 Schemaentries表与关联关系5.1 建表语句初始迁移 core/src/infra/db/migration/m20240101_000001_initial_schema.rs 中的entries表SeaORM 使用Entries常量完整字段如下列类型约束/说明idinteger自增主键uuiduuid同步与 UI 缓存兼容用的稳定标识namestring非空条目名含扩展名kindinteger非空0File / 1Directory / 2Symlinkextensionstring可空文件扩展名不含点目录为 NULLmetadata_idinteger可空外键 →user_metadata.idON DELETE SET NULLcontent_idinteger可空外键 →content_identities.idON DELETE SET NULLsizebig_integer非空文件字节数aggregate_sizebig_integer非空含全部子项的合计大小目录child_countinteger非空直接子项数量file_countinteger非空目录及其子目录中的文件总数created_at/modified_attimestamp with tz非空accessed_attimestamp with tz可空permissionsstringUnix 权限字符串inodebig_integer平台相关文件标识用于变更检测parent_idinteger自引用指向父目录的entries.idSeaORM 实体 core/src/infra/db/entities/entry.rs 与之对应并额外携带indexed_at索引/同步时间戳同步水位线与volume_id所属卷卷归属设备从而推导 Entry 的设备所有权。实体定义的四个关系同文件 L33-L55为UserMetadatametadata_id → user_metadata.id、ContentIdentitycontent_id → content_identities.id、Parent自引用、Volumevolume_id → volume.id。metadata_id与content_id均使用ON DELETE SET NULL保证元数据/内容身份被删除时 Entry 仍然存活。5.2 层级查询entry_closure闭包表父子层级通过自引用parent_id表达而高效的后代/祖先查询依赖闭包表entry_closureancestor_id, descendant_id, depth。同步链路在插入 Entry 后调用rebuild_entry_closurecore/src/infra/db/entities/entry.rs删除该 Entry 作为后代的旧闭包记录 → 插入 depth0 的自引用 → 复制父节点全部祖先关系并depth1。同步回填完成后则调用rebuild_all_entry_closuresL772-L842做整体重建清空闭包表 → 批量插入自引用 → 迭代执行INSERT OR IGNORE直至无新关系超过 100 轮视为存在环报错退出。这保证了子树删除delete_subtree、位置作用域过滤等操作在同步数据上依然正确。六、同步语义设备所有权的状态复制Entry在 core/src/infra/db/entities/entry.rs 注册为设备所有device-owned的同步模型SYNC_MODEL entryexclude_fields [id, indexed_at]主键与本地水位线不外发sync_depends_on [volume, content_identity, user_metadata]确保外键目标先于 Entry 到达。关键设计同文件apply_state_changeL476-L659包括无 HLC 排序、幂等 upsert、last-write-winsEntry 只有属主设备修改因此采用基于状态的复制而非日志复制按 UUID 幂等写入UUID ↔ 本地整型主键映射foreign_key_mappingsL101-L110声明volume_id→volumes、parent_id→entries、metadata_id→user_metadata、content_id→content_identities四组外键映射同步广播前批量把整型 FK 转换为 UUID接收端再映射回本地 IDmap_sync_json_to_local墓碑保护Entry 自身或父节点已被墓碑标记时跳过应用防止孤儿节点与复活竞态L511-L560目录路径一致化同步时目录会附带directory_path绝对路径并写入directory_paths表L623-L656使不同设备上的目录拥有完全一致的寻址路径避免本地重建路径的偏差例如根目录应保留/Users/jamespine/Downloads而非Downloads删除级联apply_deletion按 UUID 找到 Entry 后调用delete_subtree级联删除整棵子树L407-L420且不产生额外墓碑。同步查询端query_for_syncL197-L397以indexed_at作为水位线采用(indexed_at, uuid)双字段游标分页保证确定性设备过滤通过JOIN volume ON volume.device_id ?实现使卷的所有权变更可自动迁移其下全部 Entry。七、验收标准核验与工程启示对照任务文档的三条验收标准可在当前源码中逐一找到证据验收标准源码证据Entry结构体存在且包含metadata_id与content_identries表列定义迁移脚本 L242-L243与实体模型 core/src/infra/db/entities/entry.rs系统能使用统一模型表示文件和目录EntryKind::{File0, Directory1, Symlink2}双端定义File聚合模型统一承载数据库 Schema 正确反映Entry模型初始迁移entries表 entry_closure表 user_metadata/content_identities外键从这套设计中可以提炼几条值得借鉴的建模原则统一载体 可空增强列文件/目录/符号链接共用一个模型metadata_id、content_id均设计为可空外键让“基础能力”与“增强能力”解耦——未哈希、未加元数据的条目也完整可用计算型聚合视图持久层保持扁平、可同步的Entry对外暴露的是按需批量组装的File聚合视图兼顾同步简单性与业务表达力作用域驱动的元数据entry_uuid与content_identity_uuid二选一的作用域设计让“单副本备注”与“全内容收藏”两种语义共存且冲突规则清晰entry 级优先同步语义与数据所有权绑定设备所有的状态复制省去冲突消解闭包表随写入即时维护墓碑与水位线indexed_at保障删除与增量同步的正确性。若要在实际工程中落地或扩展该模型可直接以 core/src/domain/file.rs、core/src/infra/db/entities/entry.rs 与 core/src/infra/db/migration/m20240101_000001_initial_schema.rs 为参照新增字段时需同步修改实体模型、领域聚合与同步字段排除清单并确保entry_closure重建逻辑覆盖新增的父子关系扩展元数据类型时优先复用custom_fieldsJSON 字段而非新增硬编码列以保持同步与迁移的最小化。【免费下载链接】spacedriveSpacedrive is an open source cross-platform file explorer, powered by a virtual distributed filesystem written in Rust.项目地址: https://gitcode.com/gh_mirrors/sp/spacedrive创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考