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
+18 -21
View File
@@ -85,11 +85,11 @@ export component ImportPage inherits Rectangle {
in property <string> survey-summary;
// --- where they are going --------------------------------------------
/// The library root. Shown, not editable — see the header comment.
in property <string> destination;
/// The folder on the server the originals go on to, once they are safely
/// on disk (FR-NC-7b). Empty when no account is signed in, which hides the
/// line rather than showing an empty one.
/// The library folder on the server — the only destination there is.
///
/// Empty means no account is signed in, and Import stays disabled: the
/// library lives on the server, so an import without one has nowhere to
/// go at all.
in property <string> upload-target;
in-out property <string> folder-template;
/// The template expanded against a real date, so the answer to "what will
@@ -272,27 +272,24 @@ export component ImportPage inherits Rectangle {
spacing: Theme.gap;
Label { text: "Library"; }
Value {
text: root.destination;
text: root.upload-target != "" ? root.upload-target
: "not signed in";
overflow: elide;
horizontal-stretch: 1;
}
}
if root.upload-target != "": HorizontalLayout {
spacing: Theme.gap;
Label { text: "Server"; }
Value {
text: root.upload-target;
overflow: elide;
horizontal-stretch: 1;
}
}
// Said once, here, rather than beside the Import button:
// the order is the promise, and a user watching a slow
// upload needs to already know the photographs are on disk.
// The order is the promise, and a user watching a slow
// upload needs to already know the photographs are safe.
// Said here, beside the destination, rather than beside the
// Import button where it would read as a warning.
if root.upload-target != "": Caption {
text: "Copied here and verified first, then uploaded.";
text: "Copied to this computer and verified first, then uploaded. Anything that does not go up stays queued for the next import.";
wrap: word-wrap;
}
if root.upload-target == "": Caption {
text: "Sign in first — the library this imports into is on the server.";
wrap: word-wrap;
}
@@ -333,7 +330,7 @@ export component ImportPage inherits Rectangle {
// The one operation in this application with no undo,
// and the only honest place to say so is beside the
// control that arms it (FR-NC-7b).
text: "The card is emptied only after every file is verified in the library — and, on a library that syncs, after it has reached the server.";
text: "The card is emptied only of photographs the server has confirmed. Anything that does not upload keeps its card copy — which is then the only copy there is.";
wrap: word-wrap;
}