Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | 132x 132x 13x 28x 13x 11x 31x 31x 101x 20x 20x 20x 20x 5x 3x 4x 2x 1x 2x 3x 12x 12x 11x | import { unzipDiscImage } from "./archive.js";
import { stringToUint8Array } from "./binary.js";
import { loadData } from "./loader.js";
/** Splits an image reference into its schema (empty for a bare name) and the image it names. */
export function splitImage(image) {
const match = image.match(/(([^:]+):\/?\/?|[!^|])?(.*)/);
return { schema: match[2] || match[1] || "", image: match[3] };
}
/**
* Every schema a reference can start with: how it is served (`route`) and where the page says it
* came from, as a media-catalogue source key (`source`) or as words (`phrase`). A reference
* with no schema is a bare name from the built-in folder.
*/
export const Schemas = Object.freeze({
"": { route: "folder", source: "builtin" },
sth: { route: "sth", source: "sth" },
"|": { route: "sth", source: "sth" },
hfe: { route: "hfe", source: "hfe" },
bitshifters: { route: "bitshifters", source: "bitshifters" },
gd: { route: "drive", source: "gdrive" },
local: { route: "browser", source: "browser" },
"!": { route: "browser", source: "browser" },
session: { route: "session", source: "session" },
data: { route: "zipped-inline", phrase: "the URL" },
b64data: { route: "inline", phrase: "the URL" },
http: { route: "url", phrase: "the web" },
https: { route: "url", phrase: "the web" },
file: { route: "url", phrase: "a file" },
});
/** How a reference is served; a schema this page does not know is read as a folder name. */
export const routeOf = (ref) => (Schemas[splitImage(ref).schema] ?? Schemas[""]).route;
// Where a bare name is looked for, and which registered source serves the archive.
const Kinds = {
disc: { folder: "discs", sth: "sth" },
tape: { folder: "tapes", sth: "tapeSth" },
};
/** The image itself when `name` is a zip, else the bytes as given, either way as `{ name, data, ignored }`. */
export function openIfZip(name, data) {
return /\.zip/i.test(name) ? unzipDiscImage(data) : { name, data, ignored: [] };
}
/**
* Turns any image reference the URL can name into bytes, `{ name, data, ignored }`,
* for a disc or a tape: the archives through the sources registered for them,
* `data:` and `b64data:` inline, `http:`, `https:` and `file:` by URL, and a
* bare name from the built-in folder. Zips are opened once, here. Nothing in
* it needs a page, so a headless machine can load the same references.
*/
export class MediaResolver {
constructor({ load = loadData } = {}) {
this.sources = {};
this.load = load;
}
/** Registers the fetcher behind an archive schema; each archive registers as it is constructed. */
addSource(schema, fetcher) {
this.sources[schema] = fetcher;
}
async resolve(kind, ref) {
const { image } = splitImage(ref);
const { folder, sth } = Kinds[kind];
const route = routeOf(ref);
switch (route) {
case "sth":
return this.source(sth)(image);
case "hfe":
case "bitshifters":
return { name: image, data: await this.source(route)(image), ignored: [] };
case "session":
return this.source("session")(image);
case "inline":
return { name: "disk.ssd", data: stringToUint8Array(atob(image)), ignored: [] };
case "zipped-inline":
return unzipDiscImage(stringToUint8Array(atob(image)));
case "url":
// The URL may end in query parameters, which would upset the extension check.
return openIfZip(new URL(ref).pathname.split("/").pop(), await this.load(ref));
default:
return openIfZip(image, await this.load(`${folder}/${image}`));
}
}
source(schema) {
const fetcher = this.sources[schema];
if (!fetcher) throw new Error(`No ${schema} archive is available here`);
return fetcher;
}
}
|