forked from
tokono.ma/diffuse
A music player that connects to your cloud/distributed storage.
1import * as IDB from "idb-keyval";
2
3import { ostiary, rpc } from "~/common/worker.js";
4import { CACHE_KEY_PREFIX, SCHEME } from "./constants.js";
5
6/**
7 * @import { InputActions as Actions } from "~/components/input/types.d.ts"
8 * @import { Track } from "~/definitions/types.d.ts"
9 */
10
11////////////////////////////////////////////
12// STATE
13////////////////////////////////////////////
14
15/** @type {Map<string, string>} */
16const blobUrls = new Map();
17
18////////////////////////////////////////////
19// ACTIONS
20////////////////////////////////////////////
21
22/**
23 * @type {Actions['artwork']}
24 */
25export async function artwork(_uri) {
26 return null;
27}
28
29/**
30 * @type {Actions['consult']}
31 */
32export async function consult(uriOrScheme) {
33 if (!uriOrScheme.includes("://")) {
34 return { supported: true, consult: "undetermined" };
35 }
36
37 const cached = await IDB.get(CACHE_KEY_PREFIX + uriOrScheme);
38 return { supported: true, consult: cached !== undefined };
39}
40
41/**
42 * @type {Actions['detach']}
43 */
44export async function detach({ fileUriOrScheme, tracks }) {
45 if (!fileUriOrScheme.includes("://")) {
46 if (fileUriOrScheme === SCHEME) {
47 await removeBlobs(tracks.map((t) => t.uri));
48 return [];
49 }
50 return tracks;
51 }
52
53 const remaining = tracks.filter((t) => t.uri !== fileUriOrScheme);
54 await removeBlobs([fileUriOrScheme]);
55 return remaining;
56}
57
58/**
59 * @type {Actions['groupConsult']}
60 */
61export async function groupConsult(uris) {
62 const cached = await Promise.all(
63 uris.map((uri) => IDB.get(CACHE_KEY_PREFIX + uri)),
64 );
65
66 return {
67 [SCHEME]: {
68 available: true,
69 scheme: SCHEME,
70 uris: uris.filter((_, i) => cached[i] !== undefined),
71 },
72 };
73}
74
75/**
76 * @type {Actions['list']}
77 */
78export async function list(tracks) {
79 return tracks;
80}
81
82/**
83 * @type {Actions['resolve']}
84 */
85export async function resolve({ uri }) {
86 const blob =
87 /** @type {Blob | undefined} */ (await IDB.get(CACHE_KEY_PREFIX + uri));
88 if (!blob) return undefined;
89
90 let blobUrl = blobUrls.get(uri);
91
92 if (!blobUrl) {
93 blobUrl = URL.createObjectURL(blob);
94 blobUrls.set(uri, blobUrl);
95 }
96
97 return { expiresAt: Infinity, url: blobUrl };
98}
99
100////////////////////////////////////////////
101// 🛠️
102////////////////////////////////////////////
103
104/**
105 * @param {string[]} uris
106 */
107async function removeBlobs(uris) {
108 await Promise.all(uris.map(async (uri) => {
109 const blobUrl = blobUrls.get(uri);
110 if (blobUrl) {
111 URL.revokeObjectURL(blobUrl);
112 blobUrls.delete(uri);
113 }
114 await IDB.del(CACHE_KEY_PREFIX + uri);
115 }));
116}
117
118////////////////////////////////////////////
119// ⚡️
120////////////////////////////////////////////
121
122ostiary((context) => {
123 rpc(context, {
124 artwork,
125 consult,
126 detach,
127 groupConsult,
128 list,
129 resolve,
130 });
131});