export type LibraryTrackRecord = { id: string; isrc?: string; title: string; artist: string; album?: string; albumArtist?: string; savedPath?: string; artworkUrl?: string; source?: string; addedAt: number; }; const DB_NAME = "aurora-spotirip-library"; const TRACK_STORE = "tracks"; const HISTORY_STORE = "history"; function normalize(value: string) { return value .normalize("NFKD") .toLowerCase() .replace(/[^a-z0-9]+/g, " ") .trim(); } export function trackIdentity(track: { isrc?: string; artist: string; title: string; }) { return track.isrc?.trim().toUpperCase() || `${normalize(track.artist)}::${normalize(track.title)}`; } function openDb() { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, 1); request.onupgradeneeded = () => { const db = request.result; if (!db.objectStoreNames.contains(TRACK_STORE)) { db.createObjectStore(TRACK_STORE, { keyPath: "id" }); } if (!db.objectStoreNames.contains(HISTORY_STORE)) { db.createObjectStore(HISTORY_STORE, { keyPath: "id" }); } }; request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error || new Error("Unable to open library index.")); }); } export async function findLibraryDuplicate(track: { isrc?: string; artist: string; title: string; }) { if (typeof indexedDB === "undefined") return undefined; const db = await openDb(); try { const id = trackIdentity(track); return await new Promise((resolve, reject) => { const tx = db.transaction(TRACK_STORE, "readonly"); const request = tx.objectStore(TRACK_STORE).get(id); request.onsuccess = () => resolve(request.result as LibraryTrackRecord | undefined); request.onerror = () => reject(request.error); }); } finally { db.close(); } } export async function rememberLibraryTrack( track: Omit, ) { const db = await openDb(); try { const record: LibraryTrackRecord = { ...track, id: trackIdentity(track), addedAt: Date.now(), }; await new Promise((resolve, reject) => { const tx = db.transaction([TRACK_STORE, HISTORY_STORE], "readwrite"); tx.objectStore(TRACK_STORE).put(record); tx.objectStore(HISTORY_STORE).put(record); tx.oncomplete = () => resolve(); tx.onerror = () => reject(tx.error || new Error("Unable to update library history.")); }); return record; } finally { db.close(); } } export async function listLibraryHistory() { if (typeof indexedDB === "undefined") return [] as LibraryTrackRecord[]; const db = await openDb(); try { const records = await new Promise((resolve, reject) => { const tx = db.transaction(HISTORY_STORE, "readonly"); const request = tx.objectStore(HISTORY_STORE).getAll(); request.onsuccess = () => resolve((request.result || []) as LibraryTrackRecord[]); request.onerror = () => reject(request.error); }); return records.sort((a, b) => b.addedAt - a.addedAt); } finally { db.close(); } } export async function clearLibraryHistory() { const db = await openDb(); try { await new Promise((resolve, reject) => { const tx = db.transaction(HISTORY_STORE, "readwrite"); tx.objectStore(HISTORY_STORE).clear(); tx.oncomplete = () => resolve(); tx.onerror = () => reject(tx.error); }); } finally { db.close(); } }