import { config } from './config.js'; import { state } from './state.js'; import { showNotification, emitirEventoActualizacion, mostrarSpinner, ocultarSpinner, _ } from './utils.js'; export function initDB() { return new Promise((resolve, reject) => { if (!window.indexedDB) { showNotification(_("essentialFeaturesNotSupported"), "warning"); return reject("IndexedDB not supported"); } const request = indexedDB.open(config.dbName, config.dbVersion); request.onupgradeneeded = e => { state.db = e.target.result; const transaction = e.target.transaction; const storesToCreate = ['movies', 'series', 'artists', 'photos', 'tokens', 'conexiones_locales', 'settings']; storesToCreate.forEach(storeName => { if (!state.db.objectStoreNames.contains(storeName)) { let storeOptions; if (storeName === 'settings') { storeOptions = { keyPath: 'id' }; } else { storeOptions = { keyPath: 'id', autoIncrement: true }; } const store = state.db.createObjectStore(storeName, storeOptions); if (storeName === 'conexiones_locales') { store.createIndex('tokenPrincipalIndex', 'tokenPrincipal', { unique: false }); store.createIndex('tokenSecundarioIndex', 'token', { unique: false }); } } else if (storeName === 'conexiones_locales' && transaction) { try { const connectionsStore = transaction.objectStore('conexiones_locales'); if (!connectionsStore.indexNames.contains('tokenPrincipalIndex')) { connectionsStore.createIndex('tokenPrincipalIndex', 'tokenPrincipal', { unique: false }); } if (!connectionsStore.indexNames.contains('tokenSecundarioIndex')) { connectionsStore.createIndex('tokenSecundarioIndex', 'token', { unique: false }); } } catch (indexError) { if (e.target.transaction) e.target.transaction.abort(); reject(`Failed to upgrade indexes for ${storeName}`); return; } } }); }; request.onsuccess = e => { state.db = e.target.result; state.db.onversionchange = () => { state.db.close(); alert(_("dbUpdateNeeded")); window.location.reload(); }; state.db.onerror = event => { console.error(`Database error: ${event.target.errorCode}`); reject(event.target.error); }; resolve(); }; request.onerror = e => { showNotification(_("dbAccessError"), "error"); reject(e.target.error); }; request.onblocked = () => { alert(_("dbBlocked")); }; }); } export function getFromDB(storeName) { return new Promise((resolve, reject) => { if (!state.db || !state.db.objectStoreNames.contains(storeName)) { return resolve([]); } const transaction = state.db.transaction([storeName], 'readonly'); const store = transaction.objectStore(storeName); const request = store.getAll(); request.onsuccess = e => resolve(e.target.result || []); request.onerror = e => reject(e.target.error); }); } export function clearStore(storeName) { return new Promise((resolve, reject) => { if (!state.db || !state.db.objectStoreNames.contains(storeName)) { return resolve(); } const transaction = state.db.transaction([storeName], 'readwrite'); const store = transaction.objectStore(storeName); const request = store.clear(); request.onsuccess = () => resolve(); request.onerror = event => reject(event.target.error); }); } export function addItemsToStore(storeName, items) { return new Promise((resolve, reject) => { if (!state.db || !state.db.objectStoreNames.contains(storeName)) { return reject(`Store ${storeName} not available`); } if (!Array.isArray(items) || items.length === 0) { return resolve(0); } const transaction = state.db.transaction([storeName], 'readwrite'); const store = transaction.objectStore(storeName); let successCount = 0; items.forEach(item => { if (item !== undefined && item !== null) { const request = store.put(item); request.onsuccess = () => successCount++; request.onerror = (e) => console.warn(`Error adding/updating item in ${storeName}:`, e.target.error, item); } }); transaction.oncomplete = () => resolve(successCount); transaction.onerror = event => reject(event.target.error); }); } export async function clearContentData() { showNotification(_("deletingContentData"), "info"); mostrarSpinner(); const storesToDelete = ['movies', 'series', 'artists', 'photos', 'conexiones_locales']; try { if (!state.db) throw new Error(_("dbNotAvailable")); const storesPresent = storesToDelete.filter(name => state.db.objectStoreNames.contains(name)); if (storesPresent.length === 0) { showNotification(_("noContentDataToDelete"), "info"); return; } const transaction = state.db.transaction(storesPresent, 'readwrite'); const promises = storesPresent.map(storeName => { return new Promise((resolve, reject) => { const request = transaction.objectStore(storeName).clear(); request.onsuccess = resolve; request.onerror = e => reject(e.target.error); }); }); await Promise.all(promises); showNotification(_("contentDataDeleted"), "success"); emitirEventoActualizacion(); } catch (error) { showNotification(_("errorDeletingData", error.message), "error"); } finally { ocultarSpinner(); } } export async function loadTokensToEditor() { if (!state.aceEditor) { showNotification(_("aceEditorNotAvailable"), "error"); return; } try { const tokensData = await getFromDB('tokens'); const tokenList = tokensData.map(item => item.token); const structure = { tokens: tokenList.length > 0 ? tokenList : ["Pega_Tu_X-Plex-Token_Aqui"] }; state.aceEditor.setValue(JSON.stringify(structure, null, 4), -1); state.aceEditor.focus(); } catch (error) { showNotification(_("errorLoadingTokens"), "error"); state.aceEditor.setValue(`{\n "error": "${_("errorLoadingTokensMessage", error.message)}"\n}`, -1); } } export async function saveTokensFromEditor() { if (!state.aceEditor) { showNotification(_("aceEditorNotAvailableToSave"), "error"); return; } const jsonContent = state.aceEditor.getValue(); try { const parsed = JSON.parse(jsonContent); if (!parsed || !Array.isArray(parsed.tokens)) { throw new Error(_("invalidJsonFormat")); } const tokens = [...new Set(parsed.tokens.filter(t => typeof t === 'string' && t.trim()))]; await clearStore('tokens'); if (tokens.length > 0) { await addItemsToStore('tokens', tokens.map(t => ({ token: t }))); } showNotification(_("tokensSaved"), "success"); emitirEventoActualizacion(); } catch (error) { showNotification(_("errorSavingTokens", error.message), "error"); } } export async function exportDatabase() { mostrarSpinner(); try { if (!state.db) throw new Error(_("dbNotAvailable")); const dbData = {}; const storeNames = Array.from(state.db.objectStoreNames); for (const storeName of storeNames) { dbData[storeName] = await getFromDB(storeName); } const jsonString = JSON.stringify(dbData, null, 4); const blob = new Blob([jsonString], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:]/g, ''); a.download = `CinePlex_Backup_${timestamp}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showNotification(_("dbExported"), "success"); } catch (error) { showNotification(_("errorExportingDb", error.message), "error"); } finally { ocultarSpinner(); } } export async function importDatabase(file) { if (!file) return; mostrarSpinner(); state.isImporting = true; const reader = new FileReader(); reader.onload = async (e) => { try { if (!state.db) throw new Error(_("dbNotAvailable")); const dbData = JSON.parse(e.target.result); if (typeof dbData !== 'object' || dbData === null) throw new Error(_("invalidJsonFile")); const storesToImport = Object.keys(dbData).filter(name => state.db.objectStoreNames.contains(name)); if (storesToImport.length === 0) throw new Error(_("noDataToImport")); const transaction = state.db.transaction(storesToImport, 'readwrite'); for (const storeName of storesToImport) { await new Promise((res, rej) => { const req = transaction.objectStore(storeName).clear(); req.onsuccess = res; req.onerror = rej; }); if (Array.isArray(dbData[storeName])) { for (const item of dbData[storeName]) { if (item) transaction.objectStore(storeName).put(item); } } } await new Promise((res, rej) => { transaction.oncomplete = res; transaction.onerror = e => rej(e.target.error); }); showNotification(_("dbImported"), "success"); emitirEventoActualizacion(); } catch (error) { showNotification(_("errorImportingDb", error.message), "error"); } finally { ocultarSpinner(); state.isImporting = false; } }; reader.readAsText(file); }