feedback5 WH: 自选 art — fetch-assets plans every data/backups.json unit research 07 lacks (the 72 owned 6★: avatar / portrait with E2, battle Spine Front / Back, every skill icon and skill sound, sub-profession icon, from 07's URL patterns), the 自选 summons as tokens, and a type icon per module of chess.json / backups.json (manifest modules, the loadout tiles' fallback); --add-only downloads only missing files and never rewrites, re-downloads or deletes an existing one (worktrees sharing public/assets)

Downloaded 1,295 files, 127.2 MB (133,426,089 bytes) into the shared public/assets; no existing file changed or removed. data/assets.json only gains entries: 72 chars (all with avatar, portrait, Front and Back Spine), 38 tokens (avatars; no Spine — upstream has skin variants only), 208 skill icons, 10 sub-profession icons, 102 SFX units, 131 module type icons.
This commit is contained in:
sganggs committed 2026-10-05 20:13:40 +08:00
1 parent 42920c62c5
commit 9db99aad00
7 files changed
+231 -32

No files matched your search

+10 -3
View File
@@ -10,7 +10,8 @@
// - Idempotent: an existing file is kept when its size matches the ledger entry
// of a previous download or the expected byte count from research, or (when
// neither is known) when it passes format validation. The ledger lives in
// .cache/assets-ledger.json.
// .cache/assets-ledger.json. `keepExisting` (fetch-assets --add-only) keeps
// every existing file as it is; `written` = the files this run wrote.
import { mkdir, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
@@ -39,17 +40,21 @@ export class Downloader {
* @param {number} [o.retries]
* @param {number} [o.timeoutMs]
* @param {boolean} [o.force] re-download even when files exist
* @param {boolean} [o.keepExisting] never re-download or rewrite an existing file (a checkout sharing public/assets)
* @param {(msg:string)=>void} [o.log]
* @param {typeof fetch} [o.fetchImpl]
* @param {number} [o.backoffMs] base retry delay (doubles per attempt)
*/
constructor({ root, ledgerPath, concurrency = 16, retries = 3, timeoutMs = 120000, force = false, log = console.log, fetchImpl = globalThis.fetch, backoffMs = 400 }) {
constructor({ root, ledgerPath, concurrency = 16, retries = 3, timeoutMs = 120000, force = false, keepExisting = false, log = console.log, fetchImpl = globalThis.fetch, backoffMs = 400 }) {
this.root = root;
this.ledgerPath = ledgerPath;
this.concurrency = Math.max(1, Math.min(64, Number(concurrency) || 16));
this.retries = Math.max(1, Number(retries) || 3);
this.timeoutMs = timeoutMs;
this.force = force;
this.force = force && !keepExisting;
this.keepExisting = !!keepExisting;
/** @type {Set<string>} the files (paths under root) this run wrote */
this.written = new Set();
this.log = log;
this.fetch = fetchImpl;
this.backoffMs = Math.max(0, Number(backoffMs) || 0);
@@ -87,6 +92,7 @@ export class Downloader {
let st;
try { st = await stat(abs); } catch { return -1; }
if (!st.isFile() || st.size <= 0) return -1;
if (this.keepExisting) return st.size;
const led = this.ledger.files[job.rel];
if (job.mutable) {
// Post-processed files (atlases) change size; validate content instead.
@@ -162,6 +168,7 @@ export class Downloader {
return { status: 'error', bytes: 0, error: `write failed: ${e.message}` };
}
this.ledger.files[job.rel] = { url: src, bytes: r.buf.length };
this.written.add(job.rel);
this.dirty++;
const sizeChanged = !!(job.bytes && job.bytes !== r.buf.length && !job.mutable);
return { status: 'ok', bytes: r.buf.length, url: src, sizeChanged };