265 lines
10 KiB
JavaScript
265 lines
10 KiB
JavaScript
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);
|
|
} |