Import into the library, which is on the server

There was a local destination, defaulting to ~/Pictures, and an "upload" switch
that could be turned off. That was wrong twice over. DarkRoom's library *is* a
folder on a Nextcloud server (FR-NC-6) — there is no local library — so a
user-chosen local destination built a second pile of photographs that no view
in the application ever lists, and made "where did my import go" a question
with two answers.

An import now has exactly one destination and the page asks nothing about it.
With no account there is nowhere to go at all, so Import is refused rather than
quietly filling a folder.

What lands on the device is a staging copy in a directory the app owns, the
same shape `export` uses for its outbox and for the reason its module docs
give: staging first is the only path, not a fallback for being offline. The
bytes have to reach disk before the network — streaming a card straight to the
server would let a move-import erase a card against an in-flight upload, and
would make importing impossible with no connection (FR-NC-10). A staged file is
removed once the server confirms it; one that is not confirmed stays queued, and
the next import drains it.

And the rule that was stated but never enforced: `retirable` was reported and
`dr_ingest::retire` was never called by anything, so a move-import silently
behaved as a copy. The card is now emptied by the worker, of exactly those
photographs the *server* has confirmed — not those merely written here, because
the staging copy is removed moments later and anything unconfirmed would then
exist nowhere at all.

FR-NC-7b said "copied locally first ... then queued for upload", which is a
staging area; it has been rewritten to say so in terms that do not also permit
what was built.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-22 21:09:26 +02:00
co-authored by Claude Opus 5
parent ce666c768e
commit d04087af83
8 changed files with 336 additions and 104 deletions
+13 -13
View File
@@ -62,9 +62,14 @@ pub struct Settings {
/// TRACES: FR-CAT-10 | FR-NC-7a
/// What an import defaults to, remembered between cards.
///
/// A photographer imports the same way every time — same folders, same
/// destination, same answer about the card — and retyping a template on every
/// card is the kind of friction that makes people stop using the importer.
/// A photographer imports the same way every time — same folders, same answer
/// about the card — and retyping a template on every card is the kind of
/// friction that makes people stop using the importer.
///
/// **There is no destination here.** The library is the folder on the server
/// (FR-NC-6), so an import has exactly one place to go and nothing to ask.
/// What lands on this machine is a staging copy the app owns and removes once
/// the server confirms it (FR-NC-7b), which is not a setting either.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct ImportSettings {
@@ -76,15 +81,13 @@ pub struct ImportSettings {
/// defined — so it owns the default too, and this records only a
/// deliberate departure from it.
pub folder_template: String,
/// Where originals are copied to on this machine. Empty until chosen.
///
/// A local folder even on a library that lives on a server: FR-NC-7b has
/// the bytes land and be verified here first, and the upload follow.
pub destination: String,
/// The optional second copy (FR-CAT-10). Empty means none.
///
/// The only local path an import has, and deliberately so: the library
/// lives on the server, so there is no *first* destination for the user to
/// choose. This is an extra copy kept somewhere of their choosing — an
/// external drive, another disk — and nothing reads it back.
pub backup: String,
/// Whether to send imported originals on to the server (FR-NC-7b).
pub upload: bool,
/// Whether the card is to be emptied once everything is confirmed.
///
/// **Defaults to false and is the one setting worth not remembering
@@ -109,9 +112,7 @@ impl Default for ImportSettings {
fn default() -> Self {
Self {
folder_template: String::new(),
destination: String::new(),
backup: String::new(),
upload: true,
// The one irreversible thing this application does starts off.
move_from_card: false,
verify: true,
@@ -1136,7 +1137,6 @@ mod import_settings_tests {
let s: Settings = serde_json::from_str(stored).unwrap();
assert!(s.import.verify);
assert!(s.import.skip_duplicates);
assert!(s.import.upload);
// And the irreversible one stays off.
assert!(!s.import.move_from_card);
}