Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aeb275e137 | ||
|
|
67794bf5f8 | ||
|
|
b5fcbf4400 |
@@ -158,11 +158,11 @@ output to `~/quak-backup.log`. Sunday's run adds `--verify`:
|
|||||||
|
|
||||||
A run with `--verify` does all a plain run does. It also hashes each original
|
A run with `--verify` does all a plain run does. It also hashes each original
|
||||||
already in the backup and compares the result with the content hash. The content
|
already in the backup and compares the result with the content hash. The content
|
||||||
hash is the hash of the file's bytes that Ente recorded at upload. It fetches a
|
hash is the hash of the file's bytes that Ente recorded at upload. The run
|
||||||
new copy of each original that does not match, and replaces the original once
|
fetches a new copy of each original that does not match, and replaces the
|
||||||
the copy matches. Sunday's run is the `--verify` run, rather than a second job
|
original once the copy matches. Sunday's run is the `--verify` run, rather than
|
||||||
that night, because two backups of one directory cannot run at once. The second
|
a second job that night, because two backups of one directory cannot run at
|
||||||
one exits 2 without doing anything.
|
once. The second one exits 2 without doing anything.
|
||||||
|
|
||||||
Cron runs the job with a short `PATH`, usually `/usr/bin:/bin`, so the crontab
|
Cron runs the job with a short `PATH`, usually `/usr/bin:/bin`, so the crontab
|
||||||
names quak by its full path. quak finds the saved session through `HOME`, which
|
names quak by its full path. quak finds the saved session through `HOME`, which
|
||||||
@@ -314,16 +314,16 @@ refused, because both files would land on it.
|
|||||||
#### `quak get-thumb <fileID> [--out <path>]`
|
#### `quak get-thumb <fileID> [--out <path>]`
|
||||||
|
|
||||||
Downloads and decrypts one file's thumbnail, the small JPEG Ente shows in a
|
Downloads and decrypts one file's thumbnail, the small JPEG Ente shows in a
|
||||||
grid. It works as `get` does. Without `--out`, the name is `thumb_` followed by
|
grid. It writes one file, also for a live photo. Without `--out`, the name is
|
||||||
the title.
|
`thumb_` followed by the title. In all else it works as `get` does.
|
||||||
|
|
||||||
#### `quak backup <dir> [--json] [--verify]`
|
#### `quak backup <dir> [--json] [--verify]`
|
||||||
|
|
||||||
Backs up every file in the account to `<dir>`. The layout is described under
|
Backs up every file in the account to `<dir>`. The layout is described under
|
||||||
"The backup directory". It downloads only the originals that are not yet in the
|
"The backup directory". The command downloads only the originals that are not
|
||||||
backup, and it goes on past a file that fails. A file is downloaded once,
|
yet in the backup, and it goes on past a file that fails. A file is downloaded
|
||||||
however many collections hold it. An original the cache already holds is copied
|
once, however many collections hold it. An original the cache already holds is
|
||||||
from there instead.
|
copied from there instead.
|
||||||
|
|
||||||
Progress goes to stderr: one line per original fetched, and a `FAILED` line for
|
Progress goes to stderr: one line per original fetched, and a `FAILED` line for
|
||||||
each original or album that failed. The run ends with a summary of counts and,
|
each original or album that failed. The run ends with a summary of counts and,
|
||||||
@@ -362,7 +362,8 @@ Writes the metadata quak keeps about the account, its collections and its files
|
|||||||
as JSON files under `<dir>`. The layout is described under "The metadata dump
|
as JSON files under `<dir>`. The layout is described under "The metadata dump
|
||||||
directory". Photo content is not written. With `--exif`, or its alias `--all`,
|
directory". Photo content is not written. With `--exif`, or its alias `--all`,
|
||||||
the command also fetches each file's original and records the image's own
|
the command also fetches each file's original and records the image's own
|
||||||
metadata from it (see "The metadata dump directory").
|
metadata from it (see "The metadata dump directory"). Each original it fetches
|
||||||
|
stays in the cache.
|
||||||
|
|
||||||
Ente's apps compute machine-learning data for each photo, such as the faces in
|
Ente's apps compute machine-learning data for each photo, such as the faces in
|
||||||
it (see "The JSON beside each original"). The command fetches it in requests of
|
it (see "The JSON beside each original"). The command fetches it in requests of
|
||||||
@@ -377,9 +378,9 @@ word, and does not change the exit status.
|
|||||||
|
|
||||||
#### `quak helper list-missing-thumbnails [--json]`
|
#### `quak helper list-missing-thumbnails [--json]`
|
||||||
|
|
||||||
Finds the files whose thumbnail is missing on the server. It downloads every
|
Finds the files whose thumbnail is missing on the server. It downloads the
|
||||||
thumbnail in the account, one collection at a time, newest collection first, so
|
thumbnail of every file you own, one collection at a time, newest updated first,
|
||||||
on a large account it takes a while. A thumbnail counts as missing when the
|
so on a large account it takes a while. A thumbnail counts as missing when the
|
||||||
server answers with an empty body or with HTTP 404. Any other failure is logged
|
server answers with an empty body or with HTTP 404. Any other failure is logged
|
||||||
as `[<collection>] Could not check <title>: <reason> (not reported as missing)`,
|
as `[<collection>] Could not check <title>: <reason> (not reported as missing)`,
|
||||||
and the file is left out of the list. The command exits 0 even when some files
|
and the file is left out of the list. The command exits 0 even when some files
|
||||||
@@ -477,20 +478,11 @@ before any request is sent. The command logs `Invalid file ID: <arg>` or
|
|||||||
|
|
||||||
### What quak prints
|
### What quak prints
|
||||||
|
|
||||||
stdout holds only a command's output: the listing, the JSON, or nothing. The
|
stdout holds a command's output, such as the listing or the JSON, and the
|
||||||
prompts of `login` go to stdout too. Every other line goes to stderr as a log
|
prompts of `login`. Every other line goes to stderr as a log line at one of six
|
||||||
line at one of six levels. From the quietest, the levels are fatal, error,
|
levels. From the quietest, the levels are fatal, error, warning, info, debug and
|
||||||
warning, info, debug and trace. A run prints the lines at its own level and at
|
trace. A run prints the lines at its own level and at every quieter one. The
|
||||||
every quieter one. The level is info unless `-v`, `-vv` or `-q` says otherwise.
|
level is info unless `-v`, `-vv` or `-q` says otherwise.
|
||||||
|
|
||||||
The one exception is a command line quak cannot parse, such as a missing
|
|
||||||
argument, an unknown option or `-q` with `-v`. quak refuses it with one
|
|
||||||
`error: …` line that is not a log line, and exits 1. `-q` does not hide that
|
|
||||||
line. `quak` with no command prints the usage on stderr and exits 1.
|
|
||||||
|
|
||||||
`yarn quak` prints yarn's own lines on stdout as well. To pipe a command's
|
|
||||||
output, run `yarn --silent quak files --json` or
|
|
||||||
`node dist/bin/quak.js files --json` instead.
|
|
||||||
|
|
||||||
- Fatal: why a command exits non-zero. A command that runs and exits non-zero
|
- Fatal: why a command exits non-zero. A command that runs and exits non-zero
|
||||||
logs at least one fatal line. A command that exits 1 because files or albums
|
logs at least one fatal line. A command that exits 1 because files or albums
|
||||||
@@ -516,6 +508,15 @@ output, run `yarn --silent quak files --json` or
|
|||||||
one that exits non-zero prints only its fatal lines. The prompts of `login` are
|
one that exits non-zero prints only its fatal lines. The prompts of `login` are
|
||||||
not log lines, and `-q` leaves them alone.
|
not log lines, and `-q` leaves them alone.
|
||||||
|
|
||||||
|
Two things on stderr are not log lines. A command line that quak cannot parse,
|
||||||
|
such as a missing argument, an unknown option or `-q` with `-v`, is refused with
|
||||||
|
an `error: …` message, and quak exits 1. `-q` does not hide the message. `quak`
|
||||||
|
with no command prints the usage on stderr and exits 1.
|
||||||
|
|
||||||
|
`yarn quak` prints yarn's own lines on stdout as well. To pipe a command's
|
||||||
|
output, run `yarn --silent quak files --json` or
|
||||||
|
`node dist/bin/quak.js files --json` instead.
|
||||||
|
|
||||||
With `--json`, a command's JSON on stdout takes the place of its summary on
|
With `--json`, a command's JSON on stdout takes the place of its summary on
|
||||||
stderr. For `backup`, that is the `--- Backup complete ---` line, the counts and
|
stderr. For `backup`, that is the `--- Backup complete ---` line, the counts and
|
||||||
the `Failed files:` list. For `list-missing-thumbnails`, it is
|
the `Failed files:` list. For `list-missing-thumbnails`, it is
|
||||||
@@ -688,13 +689,13 @@ The JSON file beside an original holds:
|
|||||||
|
|
||||||
Update times are in microseconds, as Ente records them.
|
Update times are in microseconds, as Ente records them.
|
||||||
|
|
||||||
The file also holds the machine-learning data Ente computed for the file, as
|
The JSON also holds the machine-learning data Ente computed for the photo, as
|
||||||
`mlData`. Ente's apps detect faces in each photo and compute a CLIP embedding
|
`mlData`. Ente's apps detect faces in each photo and compute a CLIP embedding
|
||||||
for it, a list of 512 numbers that describes what the photo shows. `mlData`
|
for it, a list of 512 numbers that describes what the photo shows. `mlData`
|
||||||
holds everything but that embedding. In its place the file holds `keywords`,
|
holds everything but the `clip` block, which holds that embedding. In its place
|
||||||
derived from the embedding as described under "Keywords". A file with no
|
the JSON holds `keywords`, derived from the embedding as described under
|
||||||
machine-learning data has neither field. A file whose data holds no usable
|
"Keywords". A file with no machine-learning data has neither field. A file whose
|
||||||
embedding has no `keywords`.
|
data holds no usable embedding has no `keywords`.
|
||||||
|
|
||||||
The backup waits for the library's fetch of machine-learning data to finish
|
The backup waits for the library's fetch of machine-learning data to finish
|
||||||
before it writes the JSON files. When a file's data cannot be fetched,
|
before it writes the JSON files. When a file's data cannot be fetched,
|
||||||
@@ -747,11 +748,11 @@ derives a file's keywords the way the app sorts it. The categories are:
|
|||||||
|
|
||||||
Each category has a query, and a text embedding of that query from Ente's CLIP
|
Each category has a query, and a text embedding of that query from Ente's CLIP
|
||||||
text model. A file's score for a category is the cosine similarity of the file's
|
text model. A file's score for a category is the cosine similarity of the file's
|
||||||
embedding and the category's: a number from -1 to 1, higher the more alike the
|
embedding and the category's. That is a number from -1 to 1; the more alike the
|
||||||
two embeddings are. The file falls in the category when its score reaches the
|
two embeddings are, the higher it is. The file falls in the category when its
|
||||||
category's minimum score. The keywords are the titles of the categories the file
|
score reaches the category's minimum score. The keywords are the titles of the
|
||||||
falls in, highest score first. A video never falls in a category marked "no
|
categories the file falls in, highest score first. A video never falls in a
|
||||||
videos", as in the app.
|
category marked "no videos", as in the app.
|
||||||
|
|
||||||
#### Collection directories
|
#### Collection directories
|
||||||
|
|
||||||
@@ -774,19 +775,21 @@ ignoring case, each gets its ID added: two albums named `Trip` become
|
|||||||
`Trip (10)/` and `Trip (11)/`. Two files in one collection whose titles match,
|
`Trip (10)/` and `Trip (11)/`. Two files in one collection whose titles match,
|
||||||
ignoring case, get their IDs added before the extension: `IMG_0001 (12345).JPG`
|
ignoring case, get their IDs added before the extension: `IMG_0001 (12345).JPG`
|
||||||
and `IMG_0001 (12346).JPG`. A collection named like another collection's JSON
|
and `IMG_0001 (12346).JPG`. A collection named like another collection's JSON
|
||||||
file, such as `Trip.json` beside `Trip`, also gets its ID added. A name too long
|
file, such as `Trip.json` beside `Trip`, also gets its ID added. A file name
|
||||||
for a file name, which most file systems limit to 255 bytes, gets its ID added
|
longer than 255 bytes, the limit on most file systems, gets its ID added too. So
|
||||||
too. It is then cut short before the ID, just far enough to fit. IDs never
|
does a collection name longer than 250 bytes, which leaves room for `.json`.
|
||||||
|
Either name is then cut short before the ID, just far enough to fit. IDs never
|
||||||
change, so a name stays the same from run to run until such a clash appears or
|
change, so a name stays the same from run to run until such a clash appears or
|
||||||
goes away.
|
goes away.
|
||||||
|
|
||||||
Each run removes every symlink in a collection's directory that points into the
|
Each run removes every symlink in a collection's directory that points into the
|
||||||
date folders and is not one of its current links, yours included. A file or
|
date folders and is not one of its current links, yours included. A file or
|
||||||
symlink of yours at the name of one of its links is replaced by the link. A
|
symlink of yours at the name of one of its links is replaced by the link. A
|
||||||
directory at that name stays, and the file goes into `failures.json`. Anything
|
directory of yours at that name stays, and the photo whose link it blocks goes
|
||||||
else you put under `collections/` stays. A run removes the directory and JSON of
|
into `failures.json`. Anything else you put under `collections/` stays. A run
|
||||||
a collection that was deleted or renamed, once the directory holds nothing else.
|
removes the directory and JSON of a collection that was deleted or renamed, once
|
||||||
One that still holds something of yours stays, with its JSON.
|
the directory holds nothing else. One that still holds something of yours stays,
|
||||||
|
with its JSON.
|
||||||
|
|
||||||
A collection whose directory or JSON cannot be written does not stop the run.
|
A collection whose directory or JSON cannot be written does not stop the run.
|
||||||
For example, a file you put there may already have the directory's name. The run
|
For example, a file you put there may already have the directory's name. The run
|
||||||
@@ -831,8 +834,9 @@ Each entry holds:
|
|||||||
|
|
||||||
- `fileID` and `title`;
|
- `fileID` and `title`;
|
||||||
- `classification`: the kind of failure;
|
- `classification`: the kind of failure;
|
||||||
- `attempts`: how many runs have tried the file;
|
- `attempts`: how many runs in a row have failed the file;
|
||||||
- `lastTriedAt`: when it was last tried, as a Unix time in milliseconds;
|
- `lastTriedAt`: when the run that last tried it finished its refresh, as a Unix
|
||||||
|
time in milliseconds;
|
||||||
- `error`: the error, as quak printed it.
|
- `error`: the error, as quak printed it.
|
||||||
|
|
||||||
The kinds are:
|
The kinds are:
|
||||||
@@ -855,8 +859,8 @@ ends, whether it succeeded or failed.
|
|||||||
|
|
||||||
The lock is a directory that
|
The lock is a directory that
|
||||||
[proper-lockfile](https://github.com/moxystudio/node-proper-lockfile) creates.
|
[proper-lockfile](https://github.com/moxystudio/node-proper-lockfile) creates.
|
||||||
While the backup runs, the backup updates the directory's modification time
|
While the backup runs, it updates the modification time of `backup.lock` every 5
|
||||||
every 5 seconds. A second backup of the directory fails at once: it prints
|
seconds. A second backup of `<dir>` fails at once: it prints
|
||||||
`quak: another backup of <dir> is running` and exits 2. A run that is killed, or
|
`quak: another backup of <dir> is running` and exits 2. A run that is killed, or
|
||||||
cut off by a crash, can leave the lock behind. Once the lock has gone 10 seconds
|
cut off by a crash, can leave the lock behind. Once the lock has gone 10 seconds
|
||||||
without an update, the next run takes it over, so nobody has to remove it by
|
without an update, the next run takes it over, so nobody has to remove it by
|
||||||
@@ -960,9 +964,9 @@ of the same name apart.
|
|||||||
`files`. Each `<fileID>.json` holds the fields a backup's JSON beside an
|
`files`. Each `<fileID>.json` holds the fields a backup's JSON beside an
|
||||||
original holds, with these differences:
|
original holds, with these differences:
|
||||||
|
|
||||||
- `imageMetadata` is written only with `--exif`. The original of every file is
|
- `imageMetadata` is written only with `--exif`. The original of every file,
|
||||||
then downloaded, videos included, and read as a backup reads it (see "The JSON
|
videos included, is then downloaded and read for its EXIF, XMP and JPEG
|
||||||
beside each original").
|
dimensions.
|
||||||
- A file whose original holds no image metadata gets no `imageMetadata` field.
|
- A file whose original holds no image metadata gets no `imageMetadata` field.
|
||||||
- An original that cannot be downloaded or read gives `imageMetadataError`
|
- An original that cannot be downloaded or read gives `imageMetadataError`
|
||||||
instead.
|
instead.
|
||||||
@@ -1053,14 +1057,14 @@ still in flight.
|
|||||||
`"refresh"`, `"fetchMLData"` for a machine-learning fetch, and
|
`"refresh"`, `"fetchMLData"` for a machine-learning fetch, and
|
||||||
`"precacheThumbnails"` or `"precacheOriginals"` for the two background
|
`"precacheThumbnails"` or `"precacheOriginals"` for the two background
|
||||||
downloads. Its `status` is `"started"` as the work starts and `"done"` as it
|
downloads. Its `status` is `"started"` as the work starts and `"done"` as it
|
||||||
ends. A refresh or a machine-learning fetch that fails ends with `"failed"` and
|
ends. A refresh or a machine-learning fetch that fails ends with `"failed"`
|
||||||
the error message instead. In between, a refresh sends `"progress"` after each
|
instead, with the message in `error`. In between, a refresh sends `"progress"`
|
||||||
collection whose files it fetched: `fetched` is the number of collections
|
after each collection whose files it fetched: `fetched` is the number of
|
||||||
fetched so far, and `total` the number it will fetch. A refresh fetches the
|
collections fetched so far, and `total` the number it will fetch. A refresh
|
||||||
files of each collection that is new or changed since the cache's copy, so on an
|
fetches the files of each collection that is new or changed since the cache's
|
||||||
empty cache it fetches those of every collection. The `"done"` event of a
|
copy, so on an empty cache it fetches those of every collection. The `"done"`
|
||||||
machine-learning fetch, and of each background download, also carries `fetched`:
|
event of a machine-learning fetch, and of each background download, also carries
|
||||||
how many items it newly stored.
|
`fetched`: how many items it newly stored.
|
||||||
|
|
||||||
`lib.status()` returns a `LibraryStatus`. It holds the collection and file
|
`lib.status()` returns a `LibraryStatus`. It holds the collection and file
|
||||||
counts. `lastRefreshAt` is the time of the last refresh that succeeded, and
|
counts. `lastRefreshAt` is the time of the last refresh that succeeded, and
|
||||||
@@ -1075,10 +1079,10 @@ cached, so the next fetch asks for it again. The reasons are kept in memory
|
|||||||
only.
|
only.
|
||||||
|
|
||||||
`lib.close()` stops the background timer. It can be called more than once. A
|
`lib.close()` stops the background timer. It can be called more than once. A
|
||||||
refresh in flight is left to finish, with its write to the cache. The promise
|
refresh in flight is left to finish, with its write to the cache. `close()`
|
||||||
`close()` returns resolves once that refresh, the machine-learning fetch and the
|
returns a promise. It resolves once that refresh, the machine-learning fetch and
|
||||||
background downloads already running have all finished. The cache directory can
|
the background downloads already running have all finished. The cache directory
|
||||||
then be removed. After `close()`, `fresh()`, `freshCollections()`,
|
can then be removed. After `close()`, `fresh()`, `freshCollections()`,
|
||||||
`freshFiles()` and `lib.backup()` reject with `the library is closed`.
|
`freshFiles()` and `lib.backup()` reject with `the library is closed`.
|
||||||
|
|
||||||
### Default reads and fresh reads
|
### Default reads and fresh reads
|
||||||
@@ -1183,11 +1187,11 @@ progress of that one file.
|
|||||||
keyed by tag name, each as exifreader decodes it with its `id`, `value`,
|
keyed by tag name, each as exifreader decodes it with its `id`, `value`,
|
||||||
`description` and `computed` value. For example, `Make` is
|
`description` and `computed` value. For example, `Make` is
|
||||||
`{ id: 271, value: ["Example"], description: "Example", computed: "Example" }`.
|
`{ id: 271, value: ["Example"], description: "Example", computed: "Example" }`.
|
||||||
A tag exifreader has no name for is keyed `undefined-<tag number>`. The tags
|
A tag that exifreader has no name for is keyed `undefined-<tag number>`. The
|
||||||
of the thumbnail embedded in the file are under `Thumbnail`, so they cannot
|
tags of the thumbnail embedded in the file are under `Thumbnail`, so they
|
||||||
hide the main image's tags of the same name. The thumbnail image itself is
|
cannot hide the main image's tags of the same name. The thumbnail image itself
|
||||||
left out. Any file exifreader cannot read gives `{}`, and a video gives `{}`
|
is left out. Any file exifreader cannot read gives `{}`, and a video gives
|
||||||
without being downloaded.
|
`{}` without being downloaded.
|
||||||
- `await photo.make(opts?)` returns one common EXIF field, picked from the tags
|
- `await photo.make(opts?)` returns one common EXIF field, picked from the tags
|
||||||
`exif()` returns and typed as in `PhotoExif`, or `undefined` when the file
|
`exif()` returns and typed as in `PhotoExif`, or `undefined` when the file
|
||||||
lacks it. These methods do the same for their fields:
|
lacks it. These methods do the same for their fields:
|
||||||
@@ -1307,8 +1311,9 @@ Only the requests named above go through the pools. A refresh,
|
|||||||
with
|
with
|
||||||
`new RequestPools({ metadataConcurrency, contentConcurrency, thumbnailConcurrency })`
|
`new RequestPools({ metadataConcurrency, contentConcurrency, thumbnailConcurrency })`
|
||||||
and pass it as `pools`. Each pool serves on-demand work ahead of background
|
and pass it as `pools`. Each pool serves on-demand work ahead of background
|
||||||
work. A task submitted under a key that is still pending is not run a second
|
work. Two tasks given to `BoundedPool.run()` with the same `key` run once while
|
||||||
time. An idle pool never lends its slots to a busy one.
|
the first is pending, and both get its result. An idle pool never lends its
|
||||||
|
slots to a busy one.
|
||||||
|
|
||||||
`lib.backup()` works on as many originals at once as the content pool allows, 5
|
`lib.backup()` works on as many originals at once as the content pool allows, 5
|
||||||
by default. `quak backup` uses the default. The limit covers everything it does
|
by default. `quak backup` uses the default. The limit covers everything it does
|
||||||
@@ -1378,9 +1383,9 @@ They differ because a download's length depends on the file and the link. A
|
|||||||
whole-transfer deadline short enough to catch a hung connection would cancel a
|
whole-transfer deadline short enough to catch a hung connection would cancel a
|
||||||
large video on a slow link that is still making progress. The download deadline
|
large video on a slow link that is still making progress. The download deadline
|
||||||
restarts each time bytes arrive, so a slow download runs as long as it keeps
|
restarts each time bytes arrive, so a slow download runs as long as it keeps
|
||||||
moving, and one that stalls is aborted after 60 seconds of silence. It covers
|
moving, and one that stalls is aborted after 60 seconds of silence. The download
|
||||||
the wait for the headers as well as the body. There is no limit on the total
|
deadline covers the wait for the headers as well as the body. There is no limit
|
||||||
length of a download.
|
on the total length of a download.
|
||||||
|
|
||||||
A request that changes state is not blindly replayed. Every `POST` and `PUT` in
|
A request that changes state is not blindly replayed. Every `POST` and `PUT` in
|
||||||
the endpoint list under "HTTP API" goes through this rule. Verifying a second
|
the endpoint list under "HTTP API" goes through this rule. Verifying a second
|
||||||
@@ -1402,13 +1407,13 @@ that rule although they look like connect-time failures. On Linux they are also
|
|||||||
delivered on an established socket, after the request may have been written.
|
delivered on an established socket, after the request may have been written.
|
||||||
They stay retryable for requests that change nothing. Bun's `ConnectionRefused`
|
They stay retryable for requests that change nothing. Bun's `ConnectionRefused`
|
||||||
and `FailedToOpenSocket` are treated the same way: a request that changes
|
and `FailedToOpenSocket` are treated the same way: a request that changes
|
||||||
nothing is retried on them, and a `POST` or `PUT` is not replayed on them. When
|
nothing is retried on them, and a `POST` or `PUT` is not replayed on them. Bun
|
||||||
a connection bun kept open from an earlier request closes before any answer, bun
|
reuses a connection it kept open from an earlier request. When that connection
|
||||||
sends the request again on a new connection. If that one fails, bun gives one of
|
closes before any answer, bun sends the request again on a new one. If that one
|
||||||
these codes although the server may already have the request. The upload of a
|
fails, bun gives one of these codes although the server may already have the
|
||||||
thumbnail to its presigned URL is exempt from the rule. It stores one whole
|
request. The upload of a thumbnail to its presigned URL is exempt from the rule.
|
||||||
object at one key in one request, so replaying it has no partial state to
|
It stores one whole object at one key in one request, so replaying it has no
|
||||||
damage.
|
partial state to damage.
|
||||||
|
|
||||||
A download is retried as a whole: the request, the reading of the body and the
|
A download is retried as a whole: the request, the reading of the body and the
|
||||||
decryption. A socket reset after the response headers have arrived surfaces in
|
decryption. A socket reset after the response headers have arrived surfaces in
|
||||||
@@ -1446,15 +1451,15 @@ working client from that snapshot without logging in again. It checks every
|
|||||||
field and each key's length first, and throws an error naming the bad field.
|
field and each key's length first, and throws an error naming the bad field.
|
||||||
|
|
||||||
`Client.fromJSON` works only once libsodium, the cryptography library quak uses,
|
`Client.fromJSON` works only once libsodium, the cryptography library quak uses,
|
||||||
is loaded. Only `Client.login` loads it. A program that restores a session in a
|
is loaded. `Client.login` loads it, and `Client.fromJSON` does not. A program
|
||||||
new process first runs `await sodium.ready`, with `sodium` imported from
|
that restores a session in a new process first runs `await sodium.ready`, with
|
||||||
`libsodium-wrappers-sumo`, which quak depends on. The exported `beginLogin`,
|
`sodium` imported from `libsodium-wrappers-sumo`, which quak depends on. The
|
||||||
`unwrapAuth`, `decryptCollection`, `decryptFile`, `downloadFile` and
|
exported `beginLogin`, `unwrapAuth`, `decryptCollection`, `decryptFile`,
|
||||||
`downloadThumbnail` need the same.
|
`downloadFile` and `downloadThumbnail` need the same.
|
||||||
|
|
||||||
`client.logout()` clears the token and zeroes the key buffers in place. After
|
`client.logout()` clears the token and zeroes the key buffers in place. After
|
||||||
it, every other method on that client throws. It does not contact the server, so
|
it, every other method on that client throws. `logout()` does not contact the
|
||||||
the token stays valid there and in any saved snapshot. Call
|
server, so the token stays valid there and in any saved snapshot. Call
|
||||||
`await client.logoutOnServer()` before `logout()` to end the session on the
|
`await client.logoutOnServer()` before `logout()` to end the session on the
|
||||||
server.
|
server.
|
||||||
|
|
||||||
@@ -1907,7 +1912,9 @@ These apply to people and to coding agents alike.
|
|||||||
- Not pinned:
|
- Not pinned:
|
||||||
- bun, which `make build-bin` takes from `nix-shell -p bun`;
|
- bun, which `make build-bin` takes from `nix-shell -p bun`;
|
||||||
- `make` and `git`, which `script/bootstrap` installs from the system
|
- `make` and `git`, which `script/bootstrap` installs from the system
|
||||||
package manager when they are missing, as every `Dockerfile` stage does.
|
package manager when they are missing, as every `Dockerfile` stage does;
|
||||||
|
- `bash` and `curl`, which `script/bootstrap` installs the same way when it
|
||||||
|
has to install Node.
|
||||||
- Follow the workflow above: branches off `next`, tests first and committed
|
- Follow the workflow above: branches off `next`, tests first and committed
|
||||||
failing, and `next` merged into `main` by sneak alone.
|
failing, and `next` merged into `main` by sneak alone.
|
||||||
- Before every commit, `make lint` and `make fmt-check` must pass. `make lint`
|
- Before every commit, `make lint` and `make fmt-check` must pass. `make lint`
|
||||||
|
|||||||
+10
-7
@@ -1,15 +1,18 @@
|
|||||||
// The CLI's log level, and the one function the commands write stderr lines
|
// The CLI's log level, and the one function the commands write stderr lines
|
||||||
// through.
|
// through.
|
||||||
//
|
//
|
||||||
// Every line quak prints on stderr is a log line at one of six levels, from
|
// Every line quak logs goes to stderr, at one of six levels, from the
|
||||||
// the quietest: fatal, error, warning, info, debug, trace. A run prints the
|
// quietest: fatal, error, warning, info, debug, trace. A run prints the lines
|
||||||
// lines at its own level and at every quieter one. Its level is info unless
|
// at its own level and at every quieter one. Its level is info unless `-v` or
|
||||||
// `-v` or `-q` asks for another (see "What quak prints" in the README). Every
|
// `-q` asks for another (see "What quak prints" in the README). A command that
|
||||||
// command that exits non-zero logs a fatal line saying why, so even `-q`, which
|
// runs and exits non-zero logs a fatal line saying why, so even `-q`, which
|
||||||
// prints only fatal lines, never leaves a non-zero exit unexplained. An error
|
// prints only fatal lines, never leaves such an exit unexplained. An error
|
||||||
// line reports something that failed, such as a file or an album, and a
|
// line reports something that failed, such as a file or an album, and a
|
||||||
// warning something that did not fail but needs the user's attention. stdout
|
// warning something that did not fail but needs the user's attention. stdout
|
||||||
// carries only a command's output and is not a log.
|
// carries a command's output and the prompts of `login`, and is not a log.
|
||||||
|
// Two things on stderr are not log lines: the `error:` message for a
|
||||||
|
// command line that quak cannot parse, and the usage printed when no command
|
||||||
|
// is given.
|
||||||
//
|
//
|
||||||
// The library itself writes no lines: its layers report through callbacks, and
|
// The library itself writes no lines: its layers report through callbacks, and
|
||||||
// the CLI logs what they report. `logRequests` gives the API client options
|
// the CLI logs what they report. `logRequests` gives the API client options
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ describe("what the library reports", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Each operation is named in plain words, as the README names it.
|
// Each operation is named in plain words.
|
||||||
it("logs a refresh's progress at info, and its start and end at debug", () => {
|
it("logs a refresh's progress at info, and its start and end at debug", () => {
|
||||||
const { log, lines } = collect();
|
const { log, lines } = collect();
|
||||||
const onProgress = logRefresh(log);
|
const onProgress = logRefresh(log);
|
||||||
|
|||||||
Reference in New Issue
Block a user