diff --git a/js/accounts/pocketbase.js b/js/accounts/pocketbase.js index 4b697c4..d926619 100644 --- a/js/accounts/pocketbase.js +++ b/js/accounts/pocketbase.js @@ -677,7 +677,35 @@ const syncManager = { user_folders: Object.values(userFolders).filter((f) => f && typeof f === 'object'), }; - await database.importData(convertedData); + // Safety check: if we had local data but merged result is completely empty, something went wrong. + // Do NOT call importData as it would wipe the user's local stores. + const hadLocalData = + localData.tracks.length > 0 || + localData.albums.length > 0 || + localData.artists.length > 0 || + localData.playlists.length > 0 || + localData.mixes.length > 0 || + localData.history.length > 0 || + localData.userPlaylists.length > 0 || + localData.userFolders.length > 0; + + const isConvertedEmpty = + convertedData.favorites_tracks.length === 0 && + convertedData.favorites_albums.length === 0 && + convertedData.favorites_artists.length === 0 && + convertedData.favorites_playlists.length === 0 && + convertedData.favorites_mixes.length === 0 && + convertedData.history_tracks.length === 0 && + convertedData.user_playlists.length === 0 && + convertedData.user_folders.length === 0; + + if (hadLocalData && isConvertedEmpty) { + console.warn( + '[PocketBase] Sync aborted: local data exists but merged result is empty. Preserving local data to prevent accidental wipe.' + ); + } else { + await database.importData(convertedData, true); + } await new Promise((resolve) => setTimeout(resolve, 300)); window.dispatchEvent(new CustomEvent('library-changed')); diff --git a/js/app.js b/js/app.js index 817722c..2d1fe0e 100644 --- a/js/app.js +++ b/js/app.js @@ -412,6 +412,13 @@ async function uploadCoverImage(file) { document.addEventListener('DOMContentLoaded', async () => { await modernSettings.waitPending(); + // Request persistent storage to reduce risk of browser wiping data on updates or cleanup + if (navigator.storage && navigator.storage.persist) { + navigator.storage.persist().catch(() => { + // Ignore errors; persistence is a best-effort request + }); + } + if (import.meta.env.DEV) { window.monochrome = { HiFiClient, diff --git a/js/db.js b/js/db.js index a249304..5c1ac0b 100644 --- a/js/db.js +++ b/js/db.js @@ -455,6 +455,25 @@ export class MusicDatabase { async importData(data, clear = false) { const db = await this.open(); + // Safety check: if clear=true but all data is empty, skip to avoid wiping existing data + if (clear) { + const allEmpty = [ + data.favorites_tracks, + data.favorites_albums, + data.favorites_artists, + data.favorites_playlists, + data.favorites_mixes, + data.history_tracks, + data.user_playlists, + data.user_folders, + ].every((arr) => !arr || (Array.isArray(arr) ? arr.length === 0 : Object.keys(arr).length === 0)); + + if (allEmpty) { + console.warn('[importData] Aborting: clear=true but all import data is empty. Existing data preserved.'); + return false; + } + } + const importStore = async (storeName, items) => { if (items === undefined) return false; @@ -488,9 +507,10 @@ export class MusicDatabase { const transaction = db.transaction(storeName, 'readwrite'); const store = transaction.objectStore(storeName); - // force clear on first sync - console.log(`Clearing ${storeName} to Make Sure Everythings Good`); - store.clear(); + if (clear) { + console.log(`[importData] Clearing ${storeName} before import`); + store.clear(); + } itemsArray.forEach((item) => { if (item.id && typeof item.id === 'string' && !isNaN(item.id)) { diff --git a/js/settings.js b/js/settings.js index 0f5ee2d..9e97abc 100644 --- a/js/settings.js +++ b/js/settings.js @@ -6573,7 +6573,7 @@ export async function initializeSettings(scrobbler, player, api, ui) { reader.onload = async (event) => { try { const data = JSON.parse(event.target.result); - await db.importData(data); + await db.importData(data, true); alert('Library imported successfully!'); window.location.reload(); // Simple way to refresh all state } catch (err) {