// Track Storage Abstraction Layer // Provides a unified interface for storing/retrieving audio blobs // Default implementation uses IndexedDB, can be swapped for Electron file API const TrackStorage = (function() { const DB_NAME = 'musicroom'; const DB_VERSION = 1; const STORE_NAME = 'tracks'; let db = null; let initPromise = null; // Initialize IndexedDB function init() { if (initPromise) return initPromise; initPromise = new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onerror = () => { console.warn('TrackStorage: IndexedDB failed to open'); resolve(false); }; request.onsuccess = () => { db = request.result; resolve(true); }; request.onupgradeneeded = (event) => { const database = event.target.result; if (!database.objectStoreNames.contains(STORE_NAME)) { database.createObjectStore(STORE_NAME, { keyPath: 'filename' }); } }; }); return initPromise; } // Check if a track is cached async function has(filename) { await init(); if (!db) return false; return new Promise((resolve) => { const transaction = db.transaction(STORE_NAME, 'readonly'); const store = transaction.objectStore(STORE_NAME); const request = store.getKey(filename); request.onsuccess = () => resolve(request.result !== undefined); request.onerror = () => resolve(false); }); } // Get a track blob, returns { blob, contentType } or null async function get(filename) { await init(); if (!db) return null; return new Promise((resolve) => { const transaction = db.transaction(STORE_NAME, 'readonly'); const store = transaction.objectStore(STORE_NAME); const request = store.get(filename); request.onsuccess = () => { const result = request.result; if (result) { resolve({ blob: result.blob, contentType: result.contentType }); } else { resolve(null); } }; request.onerror = () => resolve(null); }); } // Store a track blob async function set(filename, blob, contentType) { await init(); if (!db) return false; return new Promise((resolve) => { const transaction = db.transaction(STORE_NAME, 'readwrite'); const store = transaction.objectStore(STORE_NAME); const request = store.put({ filename, blob, contentType, cachedAt: Date.now() }); request.onsuccess = () => resolve(true); request.onerror = () => resolve(false); }); } // Remove a track from cache async function remove(filename) { await init(); if (!db) return false; return new Promise((resolve) => { const transaction = db.transaction(STORE_NAME, 'readwrite'); const store = transaction.objectStore(STORE_NAME); const request = store.delete(filename); request.onsuccess = () => resolve(true); request.onerror = () => resolve(false); }); } // Clear all cached tracks async function clear() { await init(); if (!db) return false; return new Promise((resolve) => { const transaction = db.transaction(STORE_NAME, 'readwrite'); const store = transaction.objectStore(STORE_NAME); const request = store.clear(); request.onsuccess = () => resolve(true); request.onerror = () => resolve(false); }); } // List all cached track filenames async function list() { await init(); if (!db) return []; return new Promise((resolve) => { const transaction = db.transaction(STORE_NAME, 'readonly'); const store = transaction.objectStore(STORE_NAME); const request = store.getAllKeys(); request.onsuccess = () => resolve(request.result || []); request.onerror = () => resolve([]); }); } // Get storage stats async function getStats() { await init(); if (!db) return { count: 0, totalSize: 0 }; return new Promise((resolve) => { const transaction = db.transaction(STORE_NAME, 'readonly'); const store = transaction.objectStore(STORE_NAME); const request = store.getAll(); request.onsuccess = () => { const tracks = request.result || []; const totalSize = tracks.reduce((sum, t) => sum + (t.blob?.size || 0), 0); resolve({ count: tracks.length, totalSize }); }; request.onerror = () => resolve({ count: 0, totalSize: 0 }); }); } return { init, has, get, set, remove, clear, list, getStats }; })();