You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
6.5 KiB
210 lines
6.5 KiB
let db;
|
|
let dbReadyPromise;
|
|
let dbReadyResolve;
|
|
import { showModal } from "./modal.js";
|
|
|
|
// Initialize the promise that tracks DB readiness
|
|
dbReadyPromise = new Promise((resolve) => {
|
|
dbReadyResolve = resolve;
|
|
});
|
|
|
|
// Initializes the IndexedDB database.
|
|
export function initDB() {
|
|
const request = indexedDB.open("visualizerDB", 2);
|
|
|
|
request.onupgradeneeded = function (event) {
|
|
const db = event.target.result;
|
|
if (!db.objectStoreNames.contains("files")) {
|
|
db.createObjectStore("files");
|
|
}
|
|
// Create the new store for manual offsets
|
|
if (!db.objectStoreNames.contains("manualOffsets")) {
|
|
db.createObjectStore("manualOffsets");
|
|
}
|
|
};
|
|
|
|
request.onsuccess = function (event) {
|
|
db = event.target.result;
|
|
console.log("Database initialized");
|
|
dbReadyResolve(db); // Signal that DB is ready
|
|
};
|
|
|
|
request.onerror = function (event) {
|
|
console.error("IndexedDB error:", event.target.errorCode);
|
|
// If DB fails, we resolve with null so operations can proceed (gracefully failing to cache)
|
|
dbReadyResolve(null);
|
|
};
|
|
|
|
return dbReadyPromise;
|
|
}
|
|
|
|
// Ensure DB is ready before returning it
|
|
async function getDB() {
|
|
if (db) return db;
|
|
return await dbReadyPromise;
|
|
}
|
|
|
|
// Saves a file (Blob) along with its metadata into the IndexedDB.
|
|
export function saveFileWithMetadata(key, file) {
|
|
return new Promise(async (resolve, reject) => {
|
|
const database = await getDB();
|
|
|
|
if (!database) {
|
|
// If DB failed to initialize, just warn and skip caching
|
|
console.warn("Database not available. Skipping cache save.");
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
const transaction = database.transaction(["files"], "readwrite");
|
|
const store = transaction.objectStore("files");
|
|
|
|
// Store an object containing the blob and its metadata
|
|
const dataToStore = {
|
|
filename: file.name,
|
|
size: file.size,
|
|
type: file.type,
|
|
blob: file
|
|
};
|
|
|
|
const request = store.put(dataToStore, key);
|
|
|
|
request.onsuccess = () => {
|
|
console.log(`File '${file.name}' saved to DB with metadata.`);
|
|
resolve();
|
|
};
|
|
|
|
// Gracefully handle errors, especially quota limits
|
|
transaction.onerror = (event) => {
|
|
if (event.target.error.name === 'QuotaExceededError') {
|
|
showModal("Could not cache file: Browser storage quota exceeded. The app will still work for this session, but files won't be saved for next time.");
|
|
resolve(); // Resolve anyway to let the app continue without caching
|
|
} else {
|
|
console.error(`Error saving file '${key}':`, event.target.error);
|
|
reject(event.target.error);
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
|
|
// Saves a manual offset for a specific filename.
|
|
export function saveManualOffset(filename, offset) {
|
|
return new Promise(async (resolve, reject) => {
|
|
const database = await getDB();
|
|
if (!database || !filename) {
|
|
resolve(); // Fail silently if DB is not available or filename is missing
|
|
return;
|
|
}
|
|
const transaction = database.transaction(["manualOffsets"], "readwrite");
|
|
const store = transaction.objectStore("manualOffsets");
|
|
const request = store.put(offset, filename); // Key is filename, value is offset
|
|
|
|
request.onsuccess = () => {
|
|
console.log(`Manual offset ${offset}ms saved for '${filename}'.`);
|
|
resolve();
|
|
};
|
|
request.onerror = (e) => {
|
|
console.warn("Failed to save manual offset:", e);
|
|
resolve(); // Resolve anyway to prevent blocking
|
|
};
|
|
});
|
|
}
|
|
|
|
// Loads a manual offset for a specific filename.
|
|
export function loadManualOffset(filename) {
|
|
return new Promise(async (resolve) => {
|
|
const database = await getDB();
|
|
if (!database || !filename) {
|
|
resolve(null);
|
|
return;
|
|
}
|
|
const transaction = database.transaction(["manualOffsets"], "readonly");
|
|
const store = transaction.objectStore("manualOffsets");
|
|
const request = store.get(filename);
|
|
|
|
request.onsuccess = () => {
|
|
const result = request.result;
|
|
if (result !== undefined) {
|
|
console.log(`Found saved manual offset for '${filename}': ${result}ms`);
|
|
resolve(result);
|
|
} else {
|
|
resolve(null);
|
|
}
|
|
};
|
|
request.onerror = () => {
|
|
resolve(null);
|
|
};
|
|
});
|
|
}
|
|
|
|
// Deletes a manual offset for a specific filename.
|
|
export function deleteManualOffset(filename) {
|
|
return new Promise(async (resolve) => {
|
|
const database = await getDB();
|
|
if (!database || !filename) {
|
|
resolve();
|
|
return;
|
|
}
|
|
const transaction = database.transaction(["manualOffsets"], "readwrite");
|
|
const store = transaction.objectStore("manualOffsets");
|
|
const request = store.delete(filename);
|
|
|
|
request.onsuccess = () => {
|
|
console.log(`Manual offset for '${filename}' deleted.`);
|
|
resolve();
|
|
};
|
|
request.onerror = (e) => {
|
|
console.warn("Failed to delete manual offset:", e);
|
|
resolve();
|
|
};
|
|
});
|
|
}
|
|
|
|
// Loads a file from IndexedDB, performing checks for filename and size to ensure data integrity.
|
|
export function loadFreshFileFromDB(key, expectedFilename) {
|
|
return new Promise(async (resolve) => {
|
|
const database = await getDB();
|
|
|
|
if (!database || !expectedFilename) {
|
|
resolve(null);
|
|
return;
|
|
}
|
|
|
|
const transaction = database.transaction(["files"], "readonly");
|
|
const store = transaction.objectStore("files");
|
|
const request = store.get(key);
|
|
|
|
request.onsuccess = function () {
|
|
const cachedData = request.result;
|
|
if (!cachedData) {
|
|
console.log(`Cache miss for key '${key}': No data found.`);
|
|
resolve(null);
|
|
return;
|
|
}
|
|
|
|
// 1. Versioning Check: Do the filenames match?
|
|
if (cachedData.filename !== expectedFilename) {
|
|
console.warn(`Cache miss for key '${key}': Stale data found (Filename mismatch).`);
|
|
resolve(null);
|
|
return;
|
|
}
|
|
|
|
// 2. Integrity Check: Do the sizes match?
|
|
if (cachedData.blob.size !== cachedData.size) {
|
|
console.error(`Cache miss for key '${key}': Corrupted data found (Size mismatch).`);
|
|
resolve(null);
|
|
return;
|
|
}
|
|
|
|
// All checks passed!
|
|
console.log(`Cache hit for '${expectedFilename}'`);
|
|
resolve(cachedData.blob);
|
|
};
|
|
|
|
request.onerror = (event) => {
|
|
console.error(`Error loading file '${key}' from DB:`, event.target.error);
|
|
resolve(null);
|
|
};
|
|
});
|
|
}
|