silphius/app/util/resources.js
2024-10-02 14:39:56 -05:00

79 lines
2.0 KiB
JavaScript

import {LRUCache} from 'lru-cache';
const cache = new LRUCache({
max: 128,
});
export async function computeMissing(current, manifest) {
const missing = [];
for (const path in manifest) {
if (!current || !current[path] || current[path].hash !== manifest[path]) {
missing.push(path);
}
}
return missing;
}
const octets = [];
for (let i = 0; i < 256; ++i) {
octets.push(i.toString(16).padStart(2, '0'));
}
export async function parseResources(resources, buffer) {
const view = new DataView(buffer);
let caret = 0;
const count = view.getUint32(caret);
caret += 4;
const manifest = {};
for (let i = 0; i < count; ++i) {
let hash = '';
for (let j = 0; j < 20; ++j) {
hash += octets[view.getUint8(caret)]
caret += 1;
}
const byteLength = view.getUint32(caret);
caret += 4;
const asset = new ArrayBuffer(byteLength);
const assetView = new DataView(asset);
for (let j = 0; j < asset.byteLength; ++j) {
assetView.setUint8(j, view.getUint8(caret));
caret += 1;
}
manifest[resources[i]] = {asset, hash};
}
return manifest;
}
export async function fetchResources(paths, {signal} = {}) {
const response = await fetch('/resources/stream', {
body: JSON.stringify(paths),
method: 'post',
signal,
});
if (signal.aborted) {
return undefined;
}
return parseResources(paths, await response.arrayBuffer());
}
export async function get() {
const {get} = await import('idb-keyval');
const resources = await get('$$silphius_resources');
return resources || {};
}
export async function set(resources) {
const {set} = await import('idb-keyval');
cache.clear();
await set('$$silphius_resources', resources);
}
export async function readAsset(path) {
if (!cache.has(path)) {
const {pathname} = new URL(path, 'http://example.org');
const resourcePath = pathname.slice('/resources/'.length);
cache.set(path, get().then((resources) => resources[resourcePath]?.asset));
}
return cache.get(path);
}