quak backup writes the account and album records backup-metadata writes (closes #166)
check / check (push) Successful in 1m23s

quak backup now writes account.json with the account's email and user ID,
adds each album's ownerID, isShared, updationTime and, when present, its
magicMetadata, pubMagicMetadata and sharedMagicMetadata to the album's
JSON, and adds updationTime to each file's JSON. The fields and their order
match backup-metadata's account.json, _collection.json and per-file JSON.
The interface runBackup drives gains whoami, which the library passes
through from its client.

Model: opus-5-5
Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
This commit was merged in pull request #175.
This commit is contained in:
2026-10-06 07:13:24 +02:00
committed by clawbot
parent ab4d5d2cc2
commit 2483c85321
5 changed files with 159 additions and 10 deletions
+36 -7
View File
@@ -14,6 +14,7 @@
// the file's ML data
// collections/<name>/<title> symlink to the original
// collections/<name>.json per-collection metadata
// account.json the account's email and user ID
// failures.json durable ledger of unresolved failures
//
// A live photo's original is its image and its video, each with its own
@@ -64,7 +65,7 @@ import {
} from "./library/content.js";
import { representative } from "./library/records.js";
import type { MLData } from "./mldata-fetch.js";
import type { Collection, EnteFile } from "./model/types.js";
import type { Collection, EnteFile, FileMetadata } from "./model/types.js";
export type ProgressCallback = (message: string) => void;
@@ -108,6 +109,8 @@ export interface BackupResult {
// The slice of the library that backup drives. `Library` implements it; a test
// can drive backup with a stand-in.
export interface BackupLibrary {
// The account the library belongs to.
whoami(): { email: string; userID: number };
refresh(): Promise<void>;
listCollections(): Collection[];
listFiles(collectionID: number): EnteFile[];
@@ -363,6 +366,7 @@ const writeSidecar = (
collectionID: file.collectionID,
ownerID: file.ownerID,
metadata: file.metadata,
updationTime: file.updationTime,
};
if (file.magicMetadata) meta.magicMetadata = file.magicMetadata;
if (file.pubMagicMetadata) meta.pubMagicMetadata = file.pubMagicMetadata;
@@ -371,6 +375,29 @@ const writeSidecar = (
writeFileSync(path, JSON.stringify(meta, null, 2));
};
// The album's JSON: its basic fields, its magic metadata, and its files.
const writeAlbumJSON = (
path: string,
c: Collection,
files: { id: number; metadata: FileMetadata }[],
): void => {
const album: Record<string, unknown> = {
id: c.id,
name: c.name,
type: c.type,
ownerID: c.ownerID,
isShared: c.isShared,
updationTime: c.updationTime,
};
if (c.magicMetadata) album.magicMetadata = c.magicMetadata;
if (c.pubMagicMetadata) album.pubMagicMetadata = c.pubMagicMetadata;
if (c.sharedMagicMetadata) {
album.sharedMagicMetadata = c.sharedMagicMetadata;
}
album.files = files;
writeFileSync(path, JSON.stringify(album, null, 2));
};
export const runBackup = async (
lib: BackupLibrary,
opts: BackupOptions,
@@ -393,6 +420,11 @@ export const runBackup = async (
const collectionsDir = join(downloadDirectory, "collections");
const thumbnailsDir = join(downloadDirectory, "thumbnails");
mkdirSync(collectionsDir, { recursive: true });
const { email, userID } = lib.whoami();
writeFileSync(
join(downloadDirectory, "account.json"),
JSON.stringify({ email, userID }, null, 2),
);
if (includeThumbnails) mkdirSync(thumbnailsDir, { recursive: true });
removeLeftoverTempFiles(thumbnailsDir);
for (const dir of dateFolders(downloadDirectory)) {
@@ -618,13 +650,10 @@ export const runBackup = async (
}
}
writeFileSync(
writeAlbumJSON(
join(collectionsDir, `${colDirName}.json`),
JSON.stringify(
{ id: c.id, name: c.name, type: c.type, files: metaFiles },
null,
2,
),
c,
metaFiles,
);
}