File size: 2,388 Bytes
cd7a926
 
 
 
 
 
 
 
 
 
 
 
 
45fb44a
 
 
 
 
 
cd7a926
 
 
 
 
 
 
45fb44a
cd7a926
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// Persistent image cache for preview images.
//
// Hub `resolve/` URLs answer `Cache-Control: no-store` and redirect to a freshly signed CDN
// URL on every request, so the browser's HTTP cache never reuses them. Instead we keep the
// bytes in Cache Storage, keyed by the file's content hash from the dataset listing: a hit
// skips the network entirely, and a changed image gets a new hash (so a new entry).

const CACHE_NAME = "lora-previews-v1";
/** Synthetic request URL per content hash; never fetched, only used as a cache key. */
const keyUrl = (hash: string) => `https://preview-cache.invalid/${hash}`;

// One in-flight load per hash, shared by every <LoraPreview> showing that image.
const loads = new Map<string, Promise<string>>();
// Finished loads, readable synchronously so a remounted preview can render the image in its
// first frame instead of flashing the placeholder while a promise settles.
const ready = new Map<string, string>();

/** Object URL for an image that has already finished loading, if any. */
export const loadedImage = (hash: string) => ready.get(hash);

/** Object URL for the image (from cache, or fetched then cached). Falls back to `url` itself. */
export function cachedImage(url: string, hash: string): Promise<string> {
	let p = loads.get(hash);
	if (!p) {
		p = load(url, hash);
		loads.set(hash, p);
		p.then((src) => loads.get(hash) === p && ready.set(hash, src));
	}
	return p;
}

async function load(url: string, hash: string): Promise<string> {
	try {
		const cache = await caches.open(CACHE_NAME);
		let res = await cache.match(keyUrl(hash));
		if (!res) {
			const net = await fetch(url, { mode: "cors" });
			if (!net.ok) throw new Error(`${net.status}`);
			await cache.put(keyUrl(hash), net.clone());
			res = net;
		}
		return URL.createObjectURL(await res.blob());
	} catch {
		// Cache Storage unavailable (private mode, blocked storage) or fetch failed:
		// let the <img> load the URL directly.
		loads.delete(hash);
		return url;
	}
}

/** Drop cached images that are no longer in the dataset (replaced or removed). */
export async function pruneImageCache(keep: Iterable<string>): Promise<void> {
	try {
		const wanted = new Set([...keep].map(keyUrl));
		const cache = await caches.open(CACHE_NAME);
		for (const req of await cache.keys()) if (!wanted.has(req.url)) await cache.delete(req);
	} catch {
		// best effort
	}
}