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).
This commit is contained in:
+29
-2
@@ -91,12 +91,39 @@ removed clips.
|
||||
or otio backend; nothing is read from or written to the database
|
||||
beyond the current state.
|
||||
|
||||
## Configuration
|
||||
|
||||
Library selection and behavior are driven by the `Storage` config group
|
||||
(read by `crates/oakengine/src/storage.rs`):
|
||||
|
||||
- `Storage/Backend` — `"sqlite"` (the documented default), `"database"`
|
||||
or `"pg"` enables write-through; any other value (e.g. `"off"`)
|
||||
disables it. When the key is absent no library is configured and
|
||||
projects stay unbound (headless consumers and the test suite never
|
||||
touch the user's real library).
|
||||
- `Storage/SqlitePath` — the SQLite library file; default
|
||||
`<system data dir>/library.db` (honoring `OAK_CONFIG_DIR`).
|
||||
- `Storage/PgUrl` — the PostgreSQL connection string, used when
|
||||
`Backend = "pg"`: `user:pass@host:5432/dbname` (libpq URL form; an
|
||||
optional `postgres://`/`postgresql://` scheme is stripped). The
|
||||
resolved library URI is `oakdb+pg://<PgUrl>`.
|
||||
- `Storage/SnapshotIntervalSec` (default 600) and
|
||||
`Storage/JournalRetentionDays` (default 0 = keep forever) — see above.
|
||||
|
||||
## Multi-writer and platforms
|
||||
|
||||
v1 assumes a single writer per database (SQLite `busy_timeout`, PG row
|
||||
locks). Multi-writer collaboration is future work (M14). The default
|
||||
database is a single user-level SQLite file; PostgreSQL is selected
|
||||
with an `oakdb+pg://` URI.
|
||||
database is a single user-level SQLite file; PostgreSQL is selected with
|
||||
`Storage/Backend = "pg"` + `Storage/PgUrl`, or directly with an
|
||||
`oakdb+pg://` URI.
|
||||
|
||||
Database tests: `cargo test -p oakstorage` is green without PostgreSQL —
|
||||
the SQLite suite always runs; the PG suite (`tests/database_pg_test.rs`)
|
||||
connects to a real server when `OAK_TEST_PG_URL` is set (e.g.
|
||||
`postgres://user:pass@host:5432/db`) and skips with a note otherwise.
|
||||
The URL should point at a dedicated test database: each test resets the
|
||||
four tables.
|
||||
|
||||
See also: [M10 oakstorage manual](plans/riir/M10-oakstorage.md),
|
||||
[M13 write-through plan](plans/riir/M13-storage-live.md),
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 456 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 440 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 450 KiB After Width: | Height: | Size: 448 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 441 KiB After Width: | Height: | Size: 438 KiB |
@@ -109,6 +109,24 @@ facade 的 undo 推送路径挂钩(`oakengine_undo_push` / `undo_group_end`
|
||||
> 键缺失 = "无库配置"(工程不绑定、写穿不触发)——这是 §2"无库配置优雅
|
||||
> 降级"的默认形态,保证 headless 消费者(oak-cli)与测试进程永远不写
|
||||
> 用户的真实库。app 侧(D4/D5)在启动时显式设置该配置即可启用。
|
||||
>
|
||||
> D3 落地记录(2026-08):`oakdb+pg://` 全量走通(load/save/load_at/
|
||||
> snapshot/list/delete/duplicate/rename/export/import)。方言差异收敛在
|
||||
> 连接与 migration 两层:`crates/oakstorage/src/backends/database/
|
||||
> migration.rs` 按 `DatabaseBackend` 选 SQLite/PG DDL(PG 仅 `BIGSERIAL`
|
||||
> PK + `BIGINT` FK,其余同构;`CREATE TABLE IF NOT EXISTS` 幂等,每次
|
||||
> 连接时执行;payload 维持 TEXT,不做 BYTEA);sea-orm 实体与 save/replay
|
||||
> 逻辑两库共用零分支。`connect_pg` 先单次直连探测(pool 对被拒连接会
|
||||
> 退避重试到 acquire 超时,死库会挂起数秒),失败映射干净 E_IO。
|
||||
> **配置**:`Storage/Backend = "pg"` 启用,`Storage/PgUrl` 给连接串
|
||||
> (`user:pass@host:5432/dbname`,容忍 `postgres://` 前缀,oakdb uri
|
||||
> 剥掉);URI 的 `?project=` 选择器只在 query 含 `project=` 键时生效,
|
||||
> 否则整段(含 `?sslmode=…`)视为连接串。**测试矩阵**:SQLite 18 个
|
||||
> 常驻;PG 13 个变体(round-trip/journal 语义/快照重放/撤销/清理/管理
|
||||
> API/导入导出/选择)门控 `OAK_TEST_PG_URL`,未设置则早退打印说明
|
||||
> (CI 无 PG 也绿);非法连接串(E_INVALID,parse 期)+ 连不上(E_IO,
|
||||
> 单次探测)为常驻错误路径测试,无需真实 PG。共享 fixture 抽到
|
||||
> `tests/common/mod.rs`(建 fixture/保存加载/行检查 sqlite+pg 双探针)。
|
||||
|
||||
## 4. 项目管理器窗口(app)
|
||||
|
||||
@@ -119,6 +137,30 @@ facade 的 undo 推送路径挂钩(`oakengine_undo_push` / `undo_group_end`
|
||||
- 导入 .ove/.otio/.fcpxml 为新库行;选中工程导出为 .ove/.otio/.fcpxml。
|
||||
- 数据源走 oakstorage 会话 API(Rust 直调;需要 C ABI 时 facade 只增)。
|
||||
|
||||
> D4 落地记录(2026-08):app 只链接 `liboakengine` dylib,故数据源走
|
||||
> 新增 C ABI(`crates/oakengine/src/library.rs`,只增):
|
||||
> `oakengine_library_list`(JSON 行:uuid/name/created/modified + 派生
|
||||
> 统计)/ `_create` / `_delete` / `_rename` / `_duplicate` / `_import`
|
||||
> / `_export`(按扩展名走 oakstorage registry 分发 ove-xml 或 otio 后端)
|
||||
> / `oakengine_project_load_library`(载入并**绑定**写穿会话,与
|
||||
> `project_load` 同契约)。有副作用的 create/duplicate/import 返回 uuid
|
||||
> 用单次定长缓冲调用(不能两段式 measure-then-read——会在 C 侧执行两次)。
|
||||
> app 侧:`src/manager.rs`(`ProjectManager` 内容视图 + `ManagerEvent`
|
||||
> 请求枚举 + `NamePrompt`/`ConfirmContent` 子对话框;时间/时长格式化为
|
||||
> 无依赖纯函数)。`src/app.rs`:无 `--project` 启动时开管理器(驱动根
|
||||
> entity,不能走 `WindowHandle::update`——`spawn_modal` 的
|
||||
> `update_window` 会重入失败);`run_with` 启动时
|
||||
> `real::configure_storage()`(仅在 `Storage/Backend` 未配置时设
|
||||
> `sqlite`,路径用 facade 默认 `<数据目录>/library.db`),退出前
|
||||
> `storage_flush()`。菜单语义(提前并入 D5):保存/另存为 →
|
||||
> 导出工程文件…(⌘S,扩展名分发 ove/otio/fcpxml);打开 → 从库中打开…
|
||||
> (= 管理器)+ 打开工程文件…;新增 项目管理器…。状态栏自动保存段 →
|
||||
> 库写入状态(`storage_bound` / `last_error`,失败红色)。MockEngine 带
|
||||
> 内存假库(3 行种子 + 全操作),app gpui 测试覆盖建/删/复制/重命名/
|
||||
> 导入/导出/打开;facade 侧 `it_library.rs` 5 测试(建/列/打开绑定写穿/
|
||||
> 重命名复制删除/导出导入 round-trip/禁用降级)。截图
|
||||
> docs/screenshot-manager.png + -en.png。
|
||||
|
||||
## 5. 分期与判据
|
||||
|
||||
| 期 | 内容 | 完成判据 |
|
||||
|
||||
@@ -74,11 +74,35 @@ clip 行自带时间线区间(`<range in out/>`)、媒体偏移(`<media_in
|
||||
- 导出:内存序列化经 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 用
|
||||
`oakdb+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)、
|
||||
|
||||
Reference in New Issue
Block a user