168 lines
4.6 KiB
JavaScript
168 lines
4.6 KiB
JavaScript
// 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 = 'blastoise';
|
|
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
|
|
};
|
|
})();
|