Something went wrong. Try again.
a simple but full-featured web player for OpenSubsonic music servers (Navidrome, Gonic, Ampache, etc) devins.page/tinysub
svelte typescript javascript subsonic gonic navidrome
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105const isNotFound = (err: unknown) => err instanceof DOMException && err.name === "NotFoundError";
const getRoot = () => { if (!navigator.storage?.getDirectory) { throw new Error("opfs is unavailable"); } return navigator.storage.getDirectory();};
const migratedFileName = (name: string) => { const song = name.match(/^song_(.*)$/); if (song) return `song_${encodeURIComponent(song[1])}`; const art = name.match(/^art_(.*)_(128|512)$/); return art ? `art_${encodeURIComponent(art[1])}_${art[2]}` : name;};
export class OPFSStorage { private async directory(sourceId: string, create = false) { return (await getRoot()).getDirectoryHandle(sourceId, { create }); }
async migrateLegacy(sourceId: string): Promise<void> { if (!navigator.storage?.getDirectory) return; const root = await getRoot(); const directory = await this.directory(sourceId, true); const names: string[] = []; for await (const [name, entry] of root.entries()) { if (name !== sourceId && entry.kind === "file") names.push(name); } for (const name of names) { const targetName = migratedFileName(name); const blob = await (await root.getFileHandle(name)).getFile(); let handle: FileSystemFileHandle; try { handle = await directory.getFileHandle(targetName); } catch (err) { if (!isNotFound(err)) throw err; handle = await directory.getFileHandle(targetName, { create: true }); } const writable = await handle.createWritable(); await writable.write(blob); await writable.close(); await root.removeEntry(name); } }
async writeBlob(sourceId: string, key: string, blob: Blob): Promise<void> { const handle = await (await this.directory(sourceId, true)).getFileHandle( key, { create: true }, ); const writable = await handle.createWritable(); try { await writable.write(blob); await writable.close(); } catch (err) { await writable.abort().catch(() => {}); throw err; } }
async readBlob(sourceId: string, key: string): Promise<Blob | null> { try { const handle = await (await this.directory(sourceId)).getFileHandle(key); return await handle.getFile(); } catch (err) { if (isNotFound(err)) return null; throw err; } }
async deleteBlob(sourceId: string, key: string): Promise<void> { try { await (await this.directory(sourceId)).removeEntry(key); } catch (err) { if (!isNotFound(err)) throw err; } }
async clearSource(sourceId: string): Promise<void> { if (!navigator.storage?.getDirectory) return; try { await (await getRoot()).removeEntry(sourceId, { recursive: true }); } catch (err) { if (!isNotFound(err)) throw err; } }
async clear(): Promise<void> { if (!navigator.storage?.getDirectory) return; let root: FileSystemDirectoryHandle; try { root = await getRoot(); } catch (err) { if (err instanceof DOMException && err.name === "NotSupportedError") return; throw err; } const names: string[] = []; for await (const [name] of root.entries()) names.push(name); for (const name of names) await root.removeEntry(name, { recursive: true }); }}