An album whose folder cannot be made no longer stops the backup (closes #197)
check / check (push) Waiting to run
check / check (push) Waiting to run
Each album's folder, links and JSON are written inside one try. A failure prints `FAILED album <name>: <reason>`, lists the album's ID in the new `failedAlbums` of the result and of `--json`, and makes `quak backup` exit 1; the other albums and `failures.json` are still written. An album whose folder would have another album's JSON name, ignoring case, gets its ID. A name over 255 bytes (250 for a folder, which leaves room for `.json`) gets the ID and is cut short between two characters by `shortenToBytes` in `src/filename.ts`. A link's ID goes right before its extension. Apart from its call to `uniqueNames`, the album loop is only indented into the try. Model: opus-5-5
This commit is contained in:
@@ -274,15 +274,15 @@ afterAll(() => {
|
||||
|
||||
// Run `quak backup <dir>` as `bin/quak.ts` runs it, sending its requests
|
||||
// through `fetch`, and return its exit code and everything it printed on
|
||||
// stderr. Its requests are retried as those of `quak backup` are, except that
|
||||
// a retry does not wait. Its cache is beside `dir`, so each run of one
|
||||
// directory finds the cache the run before it left, as cron's runs find the
|
||||
// user's.
|
||||
// stderr and on stdout. Its requests are retried as those of `quak backup`
|
||||
// are, except that a retry does not wait. Its cache is beside `dir`, so each
|
||||
// run of one directory finds the cache the run before it left, as cron's runs
|
||||
// find the user's.
|
||||
const backup = async (
|
||||
dir: string,
|
||||
opts: { verify?: boolean } = {},
|
||||
opts: { verify?: boolean; json?: boolean } = {},
|
||||
fetch = server,
|
||||
): Promise<{ code: number; stderr: string }> => {
|
||||
): Promise<{ code: number; stderr: string; stdout: string }> => {
|
||||
let printed = "";
|
||||
const stderr = new Writable({
|
||||
write(chunk: Buffer, _encoding, done) {
|
||||
@@ -290,8 +290,10 @@ const backup = async (
|
||||
done();
|
||||
},
|
||||
});
|
||||
let output = "";
|
||||
const stdout = new Writable({
|
||||
write(_chunk, _encoding, done) {
|
||||
write(chunk: Buffer, _encoding, done) {
|
||||
output += chunk.toString();
|
||||
done();
|
||||
},
|
||||
});
|
||||
@@ -316,7 +318,7 @@ const backup = async (
|
||||
const code = await new Promise<number>((resolve) => {
|
||||
void run(backupCommand(ctx, dir, opts), stdout, stderr, resolve);
|
||||
});
|
||||
return { code, stderr: printed };
|
||||
return { code, stderr: printed, stdout: output };
|
||||
};
|
||||
|
||||
// The ID of the file whose original a request downloads, or undefined for any
|
||||
@@ -627,6 +629,40 @@ describe("quak backup", () => {
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
// The user has put a file in collections/ with the name of the Trips
|
||||
// folder, so quak cannot make that folder. It says so on one line, writes
|
||||
// the two albums after it, and exits 1, and --json lists the album's ID
|
||||
// in `failedAlbums`. No file failed, so there is no failures.json.
|
||||
it("exits 1 when an album's folder cannot be made, writes the other albums, and lists it in --json", async () => {
|
||||
const dir = mkdtempSync(join(root, "backup-"));
|
||||
mkdirSync(join(dir, "collections"));
|
||||
writeFileSync(join(dir, "collections", "Trips"), "mine");
|
||||
|
||||
const first = await backup(dir);
|
||||
const { code, stdout } = await backup(dir, { json: true });
|
||||
|
||||
expect(first.code).toBe(1);
|
||||
expect(first.stderr).toMatch(/^FAILED album Trips: EEXIST: /m);
|
||||
expect(code).toBe(1);
|
||||
expect(JSON.parse(stdout)).toMatchObject({
|
||||
failed: 0,
|
||||
undecryptableAlbums: [],
|
||||
failedAlbums: [10],
|
||||
});
|
||||
const collections = join(dir, "collections");
|
||||
expect(readJSON(join(collections, "Family.json")).id).toBe(20);
|
||||
expect(readdirSync(join(collections, "Family")).sort()).toEqual([
|
||||
"IMG_0002.heic",
|
||||
"IMG_0002.mov",
|
||||
"beach.jpg",
|
||||
]);
|
||||
expect(readJSON(join(collections, "Friend's Wedding.json")).id).toBe(
|
||||
30,
|
||||
);
|
||||
expect(existsSync(join(collections, "Trips.json"))).toBe(false);
|
||||
expect(existsSync(join(dir, "failures.json"))).toBe(false);
|
||||
});
|
||||
|
||||
// The server no longer accepts the saved session's token, as after
|
||||
// `quak logout` elsewhere, and answers every request with 401. The run
|
||||
// stops at the refresh it starts with, says to log in again, and exits 3.
|
||||
|
||||
Reference in New Issue
Block a user