Back the catalog up daily, not only before migrations

NFR-R2 asks for the catalog to be backed up on a schedule and before
schema migrations. Only the second half existed: every backup on disk
was a pre-migration copy, and a library that never migrated was never
backed up at all.

A backup is now also taken at the end of a library sweep when the newest
one is more than a day old — the moment the catalog is quiet and a day's
collection and people edits have just been folded in — on its own
thread and its own connection, so the copy of a 130 MB file is not spent
on the UI. Whether one is due is read from the backup directory, not
the catalog, so the ordinary case costs nothing. An empty catalog is
skipped: there is nothing in it a rescan would not rebuild. Pruning to
KEEP_BACKUPS applies as before.
This commit is contained in:
2026-09-13 19:32:05 +02:00
parent f100db89ca
commit eeee3d920a
3 changed files with 146 additions and 19 deletions
+36
View File
@@ -4155,6 +4155,34 @@ fn apply_zoom(window: &AppWindow, ctl: &Rc<LibraryController>, delta: i32) {
refresh_timeline(window, catalog, ctl);
}
/// TRACES: NFR-R2
/// Take the day's catalog backup if one is due, off the UI thread.
///
/// Decided cheaply first — [`dr_catalog::recovery::backup_due`] reads a
/// directory listing, not the catalog — so the ordinary case of "backed up
/// this morning already" costs no thread and no connection.
fn spawn_scheduled_backup(ctl: &Rc<LibraryController>) {
let Some((conn, _)) = ctl.session.borrow().clone() else {
return;
};
let catalog_path = library::catalog_path(&conn.account);
if !dr_catalog::recovery::backup_due(&catalog_path) {
return;
}
std::thread::spawn(move || {
let catalog = match dr_catalog::Catalog::open(&catalog_path) {
Ok(c) => c,
Err(e) => {
log::warn!("scheduled backup: opening the catalog: {e}");
return;
}
};
if let Err(e) = dr_catalog::recovery::backup_if_due(catalog.connection(), &catalog_path) {
log::warn!("scheduled backup: {e}");
}
});
}
/// Push shards and the catalog to the server, and take what it has.
///
/// Fired after the sweep completes, when there is a finished index worth
@@ -4445,6 +4473,14 @@ fn start_sweep(window: &AppWindow, ctl: &Rc<LibraryController>) {
refresh_timeline(&w, catalog, &ctl_cb);
}
}
// TRACES: NFR-R2
// The scheduled backup, at the moment the catalog is
// quietest and a day's edits have just been folded in.
// On its own thread, with its own connection: a copy
// of a 130 MB file is a second or two the Slint loop
// must not spend, and it runs beside the sync below,
// which is only a reader of the same file.
spawn_scheduled_backup(&ctl_cb);
// Now that indexing is complete, hand the result to the
// server so a second device inherits it rather than
// repeating hours of range fetches.