Files
DarkRoom/ui/dr-ui/Cargo.toml
T
dtourolle f12aece07e Make storage pluggable, and prove it with a folder backend
`RemoteBackend` existed from the first release and bought nothing it was
designed for. Seven files in `dr-ui` constructed a `NextcloudBackend`
directly, an account *was* a server URL beside a DAV user id, the local
cache directory was named after a hostname, and the launch screen knew
that signing in meant a browser handshake. The trait was real; the seam
was documentation.

A trait over operations is only a quarter of it. Pluggable storage needs
four things, and this adds the other three:

- **Capabilities** — already there, and the reason the engine can drive
  two backends at the speed each actually runs at.
- **Configuration** — `dr_sync::Account`: where a library lives, in
  whatever form its connector addresses, with no server in it. Loads
  every existing config unchanged (`backend` defaults to `nextcloud`,
  `endpoint` is stored under its historical `server` key), and
  `Account::namespace()` reproduces the old catalog directory byte for
  byte, because changing it would abandon a catalog, its thumbnail
  shards, and the sidecars holding unsynced offline work.
- **Registration** — `BackendProvider` and `BackendRegistry`.
  `ui/dr-ui/src/remote.rs` is now the only file above `dr-sync` that
  names a connector.

`Connection` (an account plus an optional `Secret`) replaces the
credentials-and-user-id pair that was threaded through fifteen
signatures in an order that could be swapped. `Secret`'s inner string is
reachable only through `expose()` and its `Debug` prints `Secret(***)`,
so the indirect leak — a `{:?}` on anything holding one — no longer
compiles into a leak.

Nextcloud is unchanged and keeps every peculiarity: propagating ETags,
chunked upload v2, `oc:fileid`, the `oc:permissions` probe on a refused
PUT, the 423 retry classification, Login Flow v2. Those are what the
capability model exists to serve, not something to hide.

`dr-sync-folder` is the second connector: a local disk, a network mount,
an external drive, or a folder a Nextcloud client already syncs. No
account, no credential — the route that works where no secrets daemon
does. It declares `LocalEtags` rather than claiming propagation a POSIX
directory cannot provide, which costs nothing because 50k `stat` calls
are not 50k PROPFINDs. Identity is a path hash, not an inode: an inode
survives a rename but differs between devices and is reused after a
delete, so two machines would disagree about which photograph a
thumbnail belonged to. Re-deriving a thumbnail is a cost; showing the
wrong one is a bug.

docs/storage.md is the contract — the traits, the four steps to add a
backend, and what each connector declares. ARCH §8.0 and §8.4a, and
FR-NC-13, say why.
2026-08-29 09:57:52 +02:00

123 lines
5.7 KiB
TOML

[package]
name = "dr-ui"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
[dependencies]
dr-types.workspace = true
# No `readback`. S1 wired Slint's texture import, so the develop view hands
# the compositor the texture itself and there is no display round-trip left to
# gate (ARCH §6.1, AC-8). The export path reads pixels back through
# `export_pixels`, which is ungated and always was.
#
# `segment-readback` *is* on, and it is not a contradiction of the above. It
# gates the region-graph transfer that local masking is built on: once per
# image, on a worker, off the frame path. The display round-trip AC-8 forbids
# stays behind its own switch, which remains off. See `dr-gpu/src/segment.rs`.
dr-gpu = { workspace = true, features = ["segment-readback"] }
# The semantic arm and its weights, for local adjustments (FR-DEV-3, D14).
dr-segment = { workspace = true, features = ["semantic", "embedded-model"] }
dr-decode.workspace = true
serde_json.workspace = true
tokio.workspace = true
reqwest.workspace = true
dr-plat.workspace = true
dr-sync.workspace = true
dr-sync-folder.workspace = true
dr-sync-nextcloud.workspace = true
dr-export.workspace = true
dr-ingest.workspace = true
dr-film.workspace = true
dr-pipeline.workspace = true
dr-catalog.workspace = true
# The face pipeline, with the ONNX runtime: this is the layer that actually
# runs the models over the library (docs/faces.md).
dr-face = { workspace = true, features = ["inference"] }
dr-thumbs.workspace = true
# The library module writes scan results straight into the catalog, so it
# needs the same SQLite types dr-catalog exposes.
rusqlite.workspace = true
# The renderer is shared, but the backend is not: winit on desktop,
# android-activity on Android, and enabling both makes the backend selector
# pick at random. So the backend features live on the target-specific
# dependencies below rather than here.
#
# `renderer-femtovg-wgpu` rather than `renderer-femtovg`: the latter is
# FemtoVG over OpenGL, and a compositor drawing through GL cannot be handed a
# `wgpu::Texture`. Importing one requires Slint itself to be rendering with
# wgpu, and this is the FemtoVG backend that does (ARCH §6.1, spike S1).
#
# `unstable-wgpu-29` is the other half: the renderer feature makes Slint draw
# with wgpu, and this one exposes the API to say so — `BackendSelector::
# require_wgpu_29` and `Image::try_from(wgpu::Texture)`. Unstable is Slint's
# word for it; the surface is small and the alternative is the 7 ms round-trip.
#
# `renderer-femtovg` is *not* kept alongside as a fallback, though it would
# still compile. Slint's winit backend prefers the wgpu FemtoVG renderer
# whenever both are built, so the GL one would only ever be reached by someone
# setting `SLINT_BACKEND=winit-femtovg` — and on that path an imported texture
# is not drawn at all. FemtoVG-over-GL has no branch for a `wgpu::Texture`, so
# it falls through to "render this image to a buffer", gets nothing back, and
# draws nothing. A blank canvas with no error is a far worse failure than the
# one below, so the fallback is removed rather than left as a trap.
#
# Consequence worth stating plainly: the desktop app now needs a working wgpu
# adapter to open a window at all. Slint refuses a CPU adapter for this
# renderer unless `SLINT_WGPU_CPU` is set in the environment.
# TEST BUILD: the wgpu renderer features have moved to the desktop-only
# dependency below. On Android they made `AndroidWindowAdapter` choose
# `SkiaRenderer::default_wgpu_29`, and so put the app on wgpu's Vulkan
# swapchain — which hardcodes `preTransform = IDENTITY` (gfx-rs/wgpu#3345).
# Without them the Android backend uses `SkiaRenderer::default`, which on
# Android resolves to Skia over OpenGL, where the driver owns the display
# rotation and there is no transform to get wrong.
slint = { workspace = true, features = ["compat-1-2"] }
wgpu.workspace = true
anyhow.workspace = true
# `SettingsError` distinguishes an io failure from a malformed file, which the
# settings page reports differently; anyhow would flatten both to a string.
thiserror.workspace = true
log.workspace = true
pollster.workspace = true
# Runtime YAML only for `live-style`; release builds read the tokens the
# Slint compiler folded in at build time and never touch style.yaml.
serde_norway = { workspace = true, optional = true }
# Backend per platform. Slint already declares its android-activity backend
# under `cfg(target_os = "android")`, so this only has to name the feature;
# cargo resolves it away entirely on desktop.
[target.'cfg(not(target_os = "android"))'.dependencies]
slint = { workspace = true, features = [
"backend-winit",
"renderer-femtovg-wgpu",
"unstable-wgpu-29",
] }
[target.'cfg(target_os = "android")'.dependencies]
slint = { workspace = true, features = ["backend-android-activity-06"] }
# Opening the sign-in URL needs an ACTION_VIEW Intent — Android has no
# xdg-open. Version-matched to Slint's Android backend so both halves of the
# process agree on the JavaVM types; ndk-context supplies the VM and activity
# that android-activity's glue already stashed.
jni = "0.22"
ndk-context = "0.1"
[build-dependencies]
slint-build.workspace = true
# build.rs generates theme.slint from style.yaml (S2).
serde_norway.workspace = true
[features]
default = []
# Debug convenience: re-read style.yaml at startup so a palette can be tuned
# without rebuilding. Costs the constant-folding of every token, so it stays
# off by default and has no business in a release build.
live-style = ["dep:serde_norway"]
[dev-dependencies]
# The face_index batch job wants a log level from the environment; the library
# itself only ever calls `log`, and picks up whatever the application installs.
env_logger.workspace = true