CinePlex/js/db.js

265 lines
10 KiB
JavaScript
Raw Normal View History

2025-07-02 14:16:25 +02:00
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 => {
2025-07-04 09:36:40 +02:00
console.error(`Database error: ${event.target.errorCode}`);
2025-07-02 14:16:25 +02:00
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++;
2025-07-04 09:36:40 +02:00
request.onerror = (e) => console.warn(`Error adding/updating item in ${storeName}:`, e.target.error, item);
2025-07-02 14:16:25 +02:00
}
});
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);
}