quak backup refuses to run while another backup of the same directory runs, exit 2 (closes #169)
check / check (push) Successful in 3m24s
check / check (push) Successful in 3m24s
lib.backup() takes a lock, backup.lock in its download directory, made with proper-lockfile, before its refresh, and removes it when it ends. A second backup of the directory fails at once with an error naming it. quak backup takes the lock itself before it opens its library and passes lockHeld to the backup, so a refused run sends no request; it prints the error as one line and exits 2. A lock untouched for 10 seconds, left by a run that could not remove it, is taken over. Deviation: yarn.lock was regenerated by yarn add in the pinned node image. Judgement call: a run failing at its refresh leaves the directory, empty. Judgement call: a lock removed mid-run stops that run with an uncaught error, the library's default. Model: opus-5-5
This commit is contained in:
@@ -524,10 +524,10 @@ stack trace. All three exit with status 3, which means the user must run
|
||||
`quak login` again. `quak logout` is the exception: with no file it says there
|
||||
is no session and exits 0, and it handles a corrupt file or a failed server call
|
||||
as described below. `quak backup` meets an expired session on the refresh that
|
||||
starts every run, before it touches any file. A session that stops working
|
||||
partway through a backup instead fails each remaining file into `failures.json`,
|
||||
so that run exits 1 and the next one stops at its refresh with status 3. No
|
||||
command but `quak login` ever prompts.
|
||||
starts every run, before it touches any file but its lock (see "Backup layout").
|
||||
A session that stops working partway through a backup instead fails each
|
||||
remaining file into `failures.json`, so that run exits 1 and the next one stops
|
||||
at its refresh with status 3. No command but `quak login` ever prompts.
|
||||
|
||||
`quak logout` ends the session on the server, so the token in `session.json`
|
||||
stops working even in a copy of the file, and then deletes the file. If the
|
||||
@@ -621,6 +621,7 @@ the smallest does not.
|
||||
(symlink)
|
||||
<name>.json collection metadata + file list
|
||||
account.json the account's email and user ID
|
||||
backup.lock the lock a running backup holds (see below)
|
||||
failures.json files that failed and have not yet succeeded
|
||||
```
|
||||
|
||||
@@ -665,6 +666,23 @@ succeeds, or once it is no longer in the library or in the backup's scope. The
|
||||
library's `lib.backup({ includeThumbnails: true })` also writes
|
||||
`thumbnails/<fileID>.jpg` beside `collections/`; `quak backup` does not.
|
||||
|
||||
`backup.lock` keeps two backups of the same directory from running at once, such
|
||||
as a cron run that starts while the previous one is still going. A backup
|
||||
creates `<dir>` if it is missing and takes the lock before its refresh, and
|
||||
removes the lock when it ends, whether it succeeds or fails; `quak backup` takes
|
||||
it before it opens its library, so a refused run sends no request. The lock is a
|
||||
directory that
|
||||
[proper-lockfile](https://github.com/moxystudio/node-proper-lockfile) creates
|
||||
and keeps touching while the backup runs. A second backup of the directory, from
|
||||
another process or the same one, fails at once: `quak backup` prints
|
||||
`quak: another backup of <dir> is running` and exits with status 2. A run
|
||||
stopped with Ctrl-C or `kill` removes the lock as it exits. Only a run that
|
||||
cannot, such as one killed with SIGKILL or cut off by a crash or power loss,
|
||||
leaves it behind; once it has gone 10 seconds untouched, the next run takes it
|
||||
over, so nobody has to remove it. The lock is outside the date folders and
|
||||
`collections/`, so it is never taken for an original, and the removal of old
|
||||
album directories never touches it.
|
||||
|
||||
A collection's directory and JSON are named after the collection, and a symlink
|
||||
after the file's title, both with unsafe characters replaced. When two
|
||||
collections would get the same name, or two symlinks in one collection the same
|
||||
@@ -912,7 +930,12 @@ photos newest first). `lib.subscribe({ onChange })` delivers a `LibraryChange`
|
||||
`SimilarResult[]` (`{ fileID, score }`, cosine similarity, most similar first,
|
||||
default limit 20). quak bundles no text encoder, so `searchByEmbedding` takes
|
||||
a query vector the caller produced elsewhere.
|
||||
- `await lib.backup(opts?)` → `BackupResult`. It waits for a refresh as
|
||||
- `await lib.backup(opts?)` → `BackupResult`. It takes the lock in the download
|
||||
directory, and fails at once with an error whose `code` is `ELOCKED` while
|
||||
another backup of it runs. A caller can instead take the lock itself, before
|
||||
it opens its library, as `quak backup` does: `await lockBackupDirectory(dir)`
|
||||
takes it, failing the same way, and returns the function that releases it, and
|
||||
the caller passes `lockHeld: true` to the backup. It waits for a refresh as
|
||||
`fresh()` does, puts every in-scope original not already at its save path
|
||||
there as `photo.download()` does (and, with `includeThumbnails`, fetches
|
||||
thumbnails) through the content cache, waits for an ML data fetch, and
|
||||
@@ -922,8 +945,8 @@ photos newest first). `lib.subscribe({ onChange })` delivers a `LibraryChange`
|
||||
then counts it as present; one the cache already held is copied from there.
|
||||
`BackupOptions`: `downloadDirectory` (falls back to the library's),
|
||||
`includeOriginals` (default `true`), `includeThumbnails` (default `false`),
|
||||
`onlyAlbumNames`, and `onProgress`. See Backup layout above for the tree it
|
||||
writes.
|
||||
`onlyAlbumNames`, `onProgress`, and `lockHeld` (default `false`). See Backup
|
||||
layout above for the tree it writes.
|
||||
|
||||
### Request pools
|
||||
|
||||
|
||||
Reference in New Issue
Block a user