diff --git a/README.md b/README.md index 8d0e24c..d26c6e7 100644 --- a/README.md +++ b/README.md @@ -136,11 +136,12 @@ matters. After a `1`, the next run fetches each missing original again, fetches the ML data that is not cached, and rebuilds the album links. A file or album quak -could not decrypt keeps the exit code at `1` until the server's copy changes -(see "Backup layout"). An original that a `--verify` run could not read, or put -back with bytes that still do not match, stays at its save path. Only a later -`--verify` run checks it again: a run without `--verify` leaves it as it is and -takes the file out of `failures.json`, so that run can exit 0. +could not decrypt keeps the exit code at `1` until the server sends a copy that +decrypts, or deletes it (see "Backup layout"). An original that a `--verify` run +could not read, or put back with bytes that still do not match, stays at its +save path. Only a later `--verify` run checks it again: a run without `--verify` +leaves it as it is and takes the file out of `failures.json`, so that run can +exit 0. ## Examples @@ -749,7 +750,7 @@ into `failures.json` with an empty title, since its title is encrypted too, and an error that starts `could not be decrypted:` and gives the reason. A file in two albums is backed up as usual if its copy in the other album decrypted. An album the library could not decrypt has no name quak can read and no files it -can list. Each run prints one line for it, +can list. A run without `--json` prints one line for it, `FAILED album : could not be decrypted: `, and `--json` and the `BackupResult` list its ID in `undecryptableAlbums`. Every other file is backed up. Either kind keeps the exit code at 1, run after run, until the server sends diff --git a/src/backup.ts b/src/backup.ts index 662c1b0..5deae14 100644 --- a/src/backup.ts +++ b/src/backup.ts @@ -609,9 +609,9 @@ const runLockedBackup = async ( // What the library could not decrypt. An album is logged by its ID, as // its name is not known, so a run limited to some albums logs it too. A - // file in an album in scope is recorded as failed, with an empty title as - // its metadata is not known, unless a copy of it in another album - // decrypted. + // file in an album in scope is backed up from a copy of it in another + // album that decrypted, even an album out of scope. With no such copy it + // is recorded as failed, with an empty title as its metadata is not known. const scope = new Set(collections.map((c) => c.id)); const undecryptableAlbums: number[] = []; for (const record of lib.undecryptable()) { @@ -619,16 +619,18 @@ const runLockedBackup = async ( if (record.fileID === undefined) { log(`FAILED album ${record.collectionID}: ${error}`); undecryptableAlbums.push(record.collectionID); - } else if ( - scope.has(record.collectionID) && - !memberships.has(record.fileID) - ) { - recordFailure( - record.fileID, - "", - collectionName.get(record.collectionID) ?? "", - new Error(error), - ); + } else if (scope.has(record.collectionID)) { + const copies = memberships.get(record.fileID); + if (copies) { + distinct.set(record.fileID, representative(copies)); + } else { + recordFailure( + record.fileID, + "", + collectionName.get(record.collectionID) ?? "", + new Error(error), + ); + } } } diff --git a/src/library/store.ts b/src/library/store.ts index f45cb3c..dabb166 100644 --- a/src/library/store.ts +++ b/src/library/store.ts @@ -49,7 +49,7 @@ interface StoredMetadata { collectionsSinceTime: Microseconds; collections: StoredCollection[]; files: StoredFile[]; - undecryptable?: Undecryptable[]; + undecryptable: Undecryptable[]; } const encodeKey = (key: Uint8Array): string =>