import type { Consult, ConsultGrouping, GroupConsult, Track } from "@applets/core/types.d.ts"; import { isAudioFile } from "@scripts/input/common"; import { bucketId, bucketsFromTracks, buildURI, consultBucket, createClient, groupTracksByBucket, loadBuckets, parseURI, } from "./common"; import { provide, transfer } from "@scripts/common"; import { SCHEME } from "./constants"; //////////////////////////////////////////// // TASKS //////////////////////////////////////////// const actions = { consult, contextualize, groupConsult, list, resolve, }; const { tasks } = provide({ actions, tasks: actions }); export type Actions = typeof actions; export type Tasks = typeof tasks; // Tasks async function consult(fileUriOrScheme: string): Promise { if (!fileUriOrScheme.includes(":")) return { supported: true, consult: "undetermined" }; const parsed = parseURI(fileUriOrScheme); if (!parsed) return { supported: true, consult: "undetermined" }; const consult = await consultBucket(parsed.bucket); return { supported: true, consult }; } async function contextualize(tracks: Track[]) { return bucketsFromTracks(tracks); } async function groupConsult(tracks: Track[]): Promise { const groups = groupTracksByBucket(tracks); const promises = Object.entries(groups).map(async ([bucketId, { bucket, tracks }]) => { const available = await consultBucket(bucket); const grouping: ConsultGrouping = available ? { available, tracks } : { available, reason: "Bucket unavailable", tracks }; return { key: `${SCHEME}:${bucketId}`, grouping, }; }); const entries = (await Promise.all(promises)).map((entry) => [entry.key, entry.grouping]); const obj = Object.fromEntries(entries); return transfer(obj); } async function list(cachedTracks: Track[] = []) { const cache: Record> = {}; cachedTracks.forEach((t: Track) => { const parsed = parseURI(t.uri); if (!parsed) return; const bid = bucketId(parsed?.bucket); if (cache[bid]) { cache[bid][parsed.path] = t; } else { cache[bid] = { [parsed.path]: t }; } }); const buckets = await loadBuckets(); const promises = Object.values(buckets).map(async (bucket) => { const client = createClient(bucket); const bid = bucketId(bucket); const list = await Array.fromAsync( client.listObjects({ prefix: bucket.path.replace(/^\//, ""), }), ); return list .filter((l) => isAudioFile(l.key)) .map((l) => { const cachedTrack = cache[bid]?.[l.key]; const id = cachedTrack?.id || crypto.randomUUID(); const stats = cachedTrack?.stats; const tags = cachedTrack?.tags; const track: Track = { id, stats, tags, uri: buildURI(bucket, l.key), }; return track; }); }); const tracks = (await Promise.all(promises)).flat(1); return transfer(tracks); } async function resolve({ method, uri }: { method: string; uri: string }) { const parsed = parseURI(uri); if (!parsed) return undefined; const expiresInSeconds = 60 * 60 * 24 * 7; // 7 days const expiresAtSeconds = Math.round(Date.now() / 1000) + expiresInSeconds; const client = createClient(parsed.bucket); const url = await client.getPresignedUrl(method.toUpperCase() as any, parsed.path); return { expiresAt: expiresAtSeconds, url }; }