Files
oak-editor/docs/zh/project-storage.md
T
Mike-Solar 025dc88c25 feat(storage,app): PostgreSQL backend (D3) + project manager window (D4)
D3: oakdb+pg:// fully wired (shared sea-orm entities, BIGSERIAL DDL,
connect-probe instead of pool retry on dead servers); Storage/Backend=pg
+ Storage/PgUrl config; 13 OAK_TEST_PG_URL-gated PG tests (verified
against a Docker postgres:16), always-on clean-error tests otherwise.

D4: DaVinci-style project manager — list with derived stats, create/
rename/duplicate/delete (confirm)/import/export (native dialogs,
ove/otio/fcpxml), shown at startup and from the file menu; facade
oakengine_library_* exports (list/create/delete/rename/duplicate/
import/export + project_load_library that binds write-through);
save/save-as menu becomes 'export project file', open splits into
from-library/from-file; status bar shows library write state; storage
activates on app start and flushes on exit; spawn_modal reentrancy
fixed (window-callback path) with a doc note.

Also: the P1 audio test's environment probe was lost in the ffi purge;
restored on cpal (the output device is cpal now).
2026-08-16 10:39:31 +08:00

110 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 工程存储架构
[English](../project-storage.md)
Oak 的工程持久化在**数据库**(默认 SQLite,支持 PostgreSQL),采用
**写穿持久化**:每一次编辑都即时落库——没有保存按钮,也没有可丢失
的数据。.ove XML 文件、OTIO、FCPXML 是导入/导出格式,不是日常存储。
## 设计原则
- **按聚合粒度持久化,不做全关系型映射。** 数据库只存四样东西:
工程元信息、KV 设置、周期全量快照、节点粒度的命令日志。领域语义
(时间线结构、节点连接、关键帧、效果链)全部留在每个节点的 XML
payload 里。工程 = 节点图 + settings,没有第三种东西——节点粒度
因此是封闭全集。
- **单一序列化事实。** 库里的节点 XML 与 .ove 序列化器
`oaknode::serializer`)产出的是同一份文档。新功能(比如调整图层)
只需要扩展 XML schema,数据库 schema 永远不变。
- **journal 由 diff 产生,不靠命令申报。** 每条 undoable 命令成功后,
在内存里重新序列化工程并与上一状态逐节点比对;变化/新增/删除的
节点各落一行。现有和未来的命令类型自动覆盖。
- **journal 就是持久化撤销历史。** 回放按命令序应用各节点最新像;
回退按逆序应用旧像。撤销跨会话存活。
## SchemaSQLite / PostgreSQLsea-orm
```sql
projects(id PK, uuid UNIQUE, name, schema_ver, created_at, modified_at, command_seq)
settings(project_id FK, key, value, PK(project_id, key))
snapshots(project_id FK, command_seq, payload, written_at, PK(project_id, command_seq))
journal(project_id FK, seq, node_identity, kind, old_xml, new_xml, at,
PK(project_id, seq, node_identity))
```
- `snapshots.payload` 是全工程 XML,脏状态下每
`Storage/SnapshotIntervalSec`(默认 600 秒)写一份,只留最近 3 份。
它只是加载加速器——journal 自己就能从零重建工程。
- `journal` 行是整节点前后像:新增节点 `old_xml` 为 NULL,删除节点
`new_xml` 为 NULL。加载 = 取最新快照,其后按 `seq` 顺序把每个节点
替换为最新像;回退到命令 N = 逆序回写 `old_xml`
`node_identity = 0` 是 settings 伪节点。
- 每条命令一个同步事务(journal 行 + `command_seq + 1`),
`kill -9` 丢 0 条命令。
- `Storage/JournalRetentionDays`(默认 0 = 全保留)限定撤销窗口;
每行仅 KB 级。
## 时间线的表示
时间线是节点 XML 内部的引用链:
```
sequence ──<tracklists>──▶ tracklist ──<tracks>──▶ track ──<blocks>──▶ clip ──<footage>──▶ footage
```
clip 行自带时间线区间(`<range in out/>`)、媒体偏移(`<media_in>`
和素材引用;效果链是效果节点 XML 里的连接记录。加载时两阶段重连
这些 identity(见 `oaknode::serializer`),所以不需要任何连接表。
时间线编辑映射为少数节点行:移动 clip 触及它的 track 和 clip 本身;
分割新增一个节点、更新两个;ripple 编辑触及受影响的 track 并删除
被移除的 clip。
## 写穿流程
1. facade 的 undo 推送(`oakengine_undo_push`、group_end、
undo/redo/jump)成功;
2. 工程在内存里重新序列化(微秒到低毫秒级)并与上一状态 diff;
3. 一个事务写入变化节点行、`command_seq + 1``modified_at`
4. 后台线程 latest-wins 写快照;退出前排空队列。
## 导入 / 导出
- 导入:.ove / .otio / .fcpxml 由既有 oakstorage 后端解析后,以
`kind = 'import'` 的 journal 行写为新工程行。
- 导出:内存序列化经 ove-xml 或 otio 后端写出;除当前状态外不读写
数据库。
## 配置
库的选择与行为全部由 `Storage` 配置组驱动(写穿由
`crates/oakengine/src/storage.rs` 读取):
- `Storage/Backend``"sqlite"`(默认值)、`"database"``"pg"`
启用写穿;其它值(如 `"off"`)禁用。键缺失 = 无库配置,工程不
绑定库(headless 消费者与测试进程永不写用户真实库)。
- `Storage/SqlitePath` — SQLite 库文件;默认
`<系统数据目录>/library.db`(尊重 `OAK_CONFIG_DIR`)。
- `Storage/PgUrl` — PostgreSQL 连接串,`Backend = "pg"` 时使用:
`user:pass@host:5432/dbname`libpq URL 形式,带
`postgres://`/`postgresql://` 前缀也会被剥掉)。解析出的库 URI 为
`oakdb+pg://<PgUrl>`
- `Storage/SnapshotIntervalSec`(默认 600)、`Storage/JournalRetentionDays`
(默认 0 = 全保留)见上。
## 多写者与平台
v1 假设单写者(SQLite `busy_timeout`PG 行锁);多写者协作是后续
工作(M14)。默认数据库是用户级单一 SQLite 文件;PostgreSQL 通过
`Storage/Backend = "pg"` + `Storage/PgUrl` 选择,或直接用
`oakdb+pg://` 连接串 URI。
数据库测试:`cargo test -p oakstorage` 全绿无需 PostgreSQL——SQLite
套件常驻运行;PG 套件(`tests/database_pg_test.rs`)在设置了
`OAK_TEST_PG_URL`(如 `postgres://user:pass@host:5432/db`)时连接真实
PG 全量运行,未设置则跳过并打印说明。该 URL 应指向专用测试库:每个
测试会重置四张表。
另见:[M10 oakstorage 手册](plans/riir/M10-oakstorage.md)、
[M13 写穿计划](plans/riir/M13-storage-live.md)、
[工程文件格式参考](project-file-reference.md)。