Commit Graph
17 Commits
Author SHA1 Message Date
clawbot e8e8e00887 A backup whose lock is lost stops with one line, not a stack trace (closes #184)
check / check (push) Waiting to run
proper-lockfile's default onCompromised throws from a timer, so a backup
whose backup.lock was removed or taken over by another backup died with
a stack trace. lockBackupDirectory now returns the lock, its release
function and why it was lost, and passes a handler that only records
the reason. The backup checks it after the refresh and before each
original, thumbnail, file's JSON and album, and before it writes
failures.json, and then fails with "lost the lock on DIR: REASON",
which quak backup prints as one line, exiting 1. lib.backup() takes a
caller's lock as lock, in place of lockHeld: true. Releasing a lost lock
leaves backup.lock alone, since it may be another backup's.

Model: opus-5-5
2026-10-14 07:37:26 +02:00
clawbot bb3b5cd2da One album or file quak cannot decrypt no longer stops a refresh or a backup (closes #181)
check / check (push) Waiting to run
A file or album whose key, metadata or name does not decrypt used to
throw out of the refresh, so quak backup stopped before storing
anything, every run. Client.collectionsSince and filesSince now return
such a row in undecryptable with the reason, the refresh leaves it out,
and the store keeps the list in metadata.json until a later refresh
decrypts or deletes the record. lib.backup() records each such file in
failures.json unless a copy in another album decrypted, in which case it
stores the original from that copy. Each such album is listed in
BackupResult.undecryptableAlbums and printed as one FAILED album line.
quak backup exits 1 while either remains. An album or file that no
longer decrypts loses its album folder, JSON and links; its original
stays.

Model: opus-5-5
2026-10-14 07:08:57 +02:00
clawbot 288825b11a quak backup no longer refreshes its library on a timer (closes #183)
check / check (push) Waiting to run
The backup opened its library with the default 3-second refresh interval,
so for its whole run it asked the server for changes every few seconds,
and each refresh started another ML data pass. It now opens it with
refreshIntervalSeconds: Infinity. The library takes Infinity as no
background timer, since setTimeout would turn it into 1 ms. A refresh
interval of 3600, as the read commands use, would still refresh once an
hour in a backup that runs for hours.

Model: opus-5-5
2026-10-14 04:10:14 +02:00
clawbot ab28d86aaa An ID on the command line is decimal digits only (closes #185)
check / check (push) Waiting to run
files --collection, get, get-thumb and helper fix-missing-thumbnails
--file read an ID with Number(), so 0x10 meant 16, 1e3 meant 1000 and
an empty string meant 0, and --file dropped an ID it could not read.
parseID in src/cli-commands.ts now takes an ID only when it is decimal
digits and a safe integer; anything else prints "Invalid file ID: ARG"
or "Invalid collection ID: ARG" and the command exits 1. The fixer
checks every --file ID before it opens the library, so one bad ID fails
the whole command before any request.

Model: opus-5-5
2026-10-14 03:06:35 +02:00
clawbot ddf58af3cc quak backup --verify re-hashes stored originals and downloads again any that do not match (closes #168)
check / check (push) Failing after 1m22s
`--verify`, or `lib.backup({ verify: true })`, hashes each original already
at its save path as the download check does, streamed, a live photo as
`<imageHash>:<videoHash>`. A mismatch is logged, removed and put back in the
same run, and what is put back is hashed too, since a copy from the content
cache is not checked; one that still does not match, or a failed fetch, goes
into `failures.json`. A file with no recorded hash counts as unchecked. The
result, the summary and `--json` gain `verified`, `mismatched` and
`unchecked`.

Judgement call: a stored original that cannot be read for hashing is recorded as failed and left in place.
Judgement call: a copy put back that still does not match stays at its save path and is not counted as downloaded.
Judgement call: the summary prints the three counts only with `--verify`.

Model: opus-5-5
2026-10-06 22:47:26 +02:00
clawbot bd77422965 quak backup refuses to run while another backup of the same directory runs, exit 2 (closes #169)
check / check (push) Successful in 3m10s
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
2026-10-06 20:30:51 +02:00
clawbot f6317109bc An expired session exits 3 with one line saying to run quak login (closes #164)
check / check (push) Successful in 3m8s
A 401 from the server that ends a command reaches run in src/cli-run.ts as
an ApiError; run prints one line saying to run "quak login" and exits 3.
quak backup meets it on its first refresh, before it touches any file. For
the commands that load the saved session, a missing or corrupt session file
keeps its message and also exits 3.

Judgement call: quak logout is unchanged; it handles its own errors and
deletes the session file whatever the server answers.
Judgement call: quak backup still prints its two progress lines before the
error line.

Model: opus-5-5
2026-10-06 14:01:48 +02:00
clawbot 9e6deb21eb Store live photos as their image and their video (closes #107)
check / check (push) Successful in 41s
A live photo, which Ente stores as one ZIP, is unpacked as it downloads
into its image and its video, each `<fileID>.<ext>` with its extension
from the ZIP, beside `<fileID>.livephoto.json`, which names the two.
Both are checked against the recorded hash and renamed into place only
when both are complete. A ZIP with a second image or video, or whose
parts come to more than 20 times its size plus 16 MiB, is refused. The
backup and the content cache count a live photo as stored only with both
files, album folders link both, `quak get` writes both, and the content
result gives the video as `videoPath`. A ZIP an earlier version stored
is replaced.

Model: opus-5-5
2026-09-28 18:06:01 +02:00
clawbot 7740ebfd4d Test quak login and backup-metadata --exif (closes #110)
check / check (push) Successful in 42s
loginCommand now takes its login function and prompts from CliContext;
bin/quak.ts passes Client.login and the terminal prompts, so behaviour is
unchanged. Tests cover a login from QUAK_EMAIL/QUAK_PASSWORD with no
prompt, the TOTP prompt, a failed login, and the saved session's modes,
and show that --exif and --all each turn on EXIF extraction. Also rewraps
the header comment of src/cli-commands.ts.

Model: opus-5-5
2026-09-23 09:27:40 +02:00
clawbot ae76eb3f74 Write each backed-up original once and skip the precache (closes #106)
check / check (push) Successful in 49s
An original fetched for a backup is now written by the download writer
straight into the backup's originals/, and the content cache records it
there instead of keeping its own copy. One the cache already held is
still copied. `quak backup` opens its library with the thumbnail and
originals precache off, as the one-shot commands do.

Model: opus-5-5
2026-09-23 08:48:02 +02:00
clawbot f52c77f155 Refresh before backup-metadata and the thumbnail helpers answer (closes #100)
check / check (push) Successful in 1m0s
backup-metadata, helper list-missing-thumbnails and helper
fix-missing-thumbnails now await lib.fresh() before reading the library,
as collections, files, get and get-thumb already do. A file added since
the cache was written is included, and a failed refresh is thrown, so
the CLI prints one line and exits 1 instead of answering from a stale
or empty cache. The README lists them among the commands that refresh
first.

Model: opus-5-5
2026-09-23 08:11:37 +02:00
clawbot 36642f4448 Print CLI errors as one line instead of a stack trace (closes #102)
check / check (push) Successful in 1m13s
An error a command throws now reaches the user as one `quak: MESSAGE`
line on stderr, and the CLI exits 1 once output has drained. The wrapper
moved from bin/quak.ts to src/cli-run.ts so it can be tested, and
bin/quak.ts awaits program.parseAsync() so async actions are awaited.

Model: opus-5-5
2026-09-23 07:33:26 +02:00
clawbot cda57eebda Skip thumbnail repairs the server always refuses (closes #109)
check / check (push) Successful in 49s
The server accepts a new thumbnail only from the file's owner and only when
it is no larger than the thumbnail size it records. Both thumbnail helpers
now skip files another account owns without fetching them. The fixer skips
a file whose recorded thumbnail size is 0 or unknown before downloading it,
and otherwise tries smaller encodings until the encrypted thumbnail fits,
skipping the file if none does.

Model: opus-5-5
2026-09-23 07:03:33 +02:00
clawbot 4bb75ca323 quak logout ends the session on the server (closes #108)
check / check (push) Successful in 54s
logout now calls POST /users/logout with the saved token through the new
Client.logoutOnServer(), then deletes session.json even when that call
fails; in that case it says the server session could not be ended and
exits 1. It prints the account's cache directory and says it still holds
decrypted data. The default cache path moves into
defaultCacheDirectory(), shared with Library.open, so both name the same
directory.

Model: opus-5-5
2026-09-23 06:51:12 +02:00
clawbot bf3b20df2f backup-metadata: keep going when an ML data request fails (closes #101)
check / check (push) Successful in 27s
Each ML data request of up to 200 files is now tried on its own. A request
that still fails after its retries is logged, its files are written with the
reason in `mlDataError`, and the command exits 1 once the dump is complete.
`fetchMLData`, used only here, is removed in favour of a per-batch loop over
`fetchMLDataBatch`.

Model: opus-5-5
2026-09-23 05:49:38 +02:00
clawbot f1836ced57 Fix two intermittently failing library tests (closes #90)
check / check (push) Successful in 27s
Library.close() now returns a promise that resolves once the work it
started has finished: an in-flight refresh with its cache write, the ML
data fetch, and running precache sweeps. The interval test could see a
refresh's new state, close, and remove the directory while the write was
still running. Every library test and CLI command now awaits close(), and
tests hold each of the three writes open to prove close() waits for it.

The precache test waited for its stub source to be called, but the cache
records a file only after checking it on disk, so status() could lag. It
now waits for both fills to report "done".

Model: opus-5-5
2026-09-23 04:38:00 +02:00
clawbot d07692897b Move CLI commands into testable functions and test them (closes #12)
check / check (push) Successful in 27s
The command bodies in bin/quak.ts become functions in
src/cli-commands.ts that take their options and a context (output
streams, session directory, cache directory, session loader) and return
an exit code. bin/quak.ts wires them to commander and exits with that
code once stdout and stderr have drained; nothing below it calls
process.exit. test/cli/commands.test.ts drives the commands with a fake
client and temp directories. Output is unchanged.

Model: opus-5-5
2026-09-23 02:47:47 +02:00