let photoScanInterval = null; let activityScanInterval = null; /* ============================================================ UTILS ============================================================ */ function stopPhotoScanPolling() { if (photoScanInterval) { clearInterval(photoScanInterval); photoScanInterval = null; } } function stopActivityScanPolling() { if (activityScanInterval) { clearInterval(activityScanInterval); activityScanInterval = null; } } function updateScanUi(data) { const current = Number(data?.current || 0); const total = Number(data?.total || 0); const percent = Number.isFinite(Number(data?.percent)) ? Number(data.percent) : total > 0 ? Math.round((current / total) * 100) : 0; return { current, total, percent }; } /* Aggiornamento indipendente dei banner di scansione. */ function showPhotoBanner(message, options = {}) { if (window.operationBanner) { window.operationBanner.show(message, { ...options, id: "photos" }); } } function showActivityBanner(message, options = {}) { if (window.operationBanner) { window.operationBanner.show(message, { ...options, id: "activities" }); } } /* ============================================================ FOTO — SCANSIONE ============================================================ */ let photoScanRequestDone = false; let photoCacheSynced = false; let photoPollingBusy = false; async function scan() { photoScanRequestDone = false; photoCacheSynced = false; startScanStatusPolling(); if (window.operationBanner) { showPhotoBanner( "Avvio scansione foto…", { type: "info", busy: true, sticky: true } ); } try { await apiGet("/scan"); photoScanRequestDone = true; } catch (err) { photoScanRequestDone = true; console.error("Errore avvio scan foto:", err); out?.({ error: err.message }); if (window.operationBanner) { showPhotoBanner( `Errore nella scansione foto: ${err.message}`, { type: "error", sticky: true } ); } } } async function startScanStatusPolling() { stopPhotoScanPolling(); photoScanInterval = setInterval(async () => { if (photoPollingBusy) return; photoPollingBusy = true; try { const data = await apiGet(`/scanStatus/photos?ts=${Date.now()}`); const remaining = Number(data?.remaining ?? 0); const pendingCount = Number(data?.pending || 0); const processingCount = Number(data?.processing || 0); const errorsFromDb = Number(data?.errors || 0); const current = Number(data?.current || 0); const total = Number(data?.total || 0); const percent = Math.max(0, Math.min(100, Number(data?.percent || 0))); if (window.operationBanner) { window.operationBanner.setProgress("photos", percent); } let queue = null; try { queue = await apiGet(`/scanStatus/worker-queues?ts=${Date.now()}`); if (!queue?.ok) queue = null; } catch (err) { console.warn("[scan foto] lettura coda fallita:", err.message); } const pending = Number(queue?.photos || 0); const errors = Number(queue?.photosErrors || 0); const scanFinished = photoScanRequestDone; if (window.operationBanner) { let message; if (!scanFinished) { message = `Scansione foto in corso · ${pending} da elaborare · ${errors} errori`; showPhotoBanner(message, { type: errors > 0 ? "error" : "info", busy: true, sticky: true }); } else if (!photoCacheSynced) { message = `Accodamento completato · ${pending} foto da elaborare · ${errors} errori`; showPhotoBanner(message, { type: errors > 0 ? "error" : "info", busy: pending > 0, sticky: true }); } else if (pending > 0) { showPhotoBanner( `Elaborazione foto in corso: ${pending} da elaborare · ${errors} errori`, { type: errors > 0 ? "error" : "info", busy: true, sticky: true } ); } else { stopPhotoScanPolling(); showPhotoBanner( errors > 0 ? `Elaborazione terminata con ${errors} errori nella coda foto` : "Scansione ed elaborazione foto completate", { type: errors > 0 ? "error" : "success", busy: false, sticky: errors > 0, duration: 5000 } ); } } if (scanFinished && !photoCacheSynced) { if (window.AdminCacheSync?.progressiveSyncPhotosCacheFromAdmin) { const result = await AdminCacheSync.progressiveSyncPhotosCacheFromAdmin(); console.log("[admin scan foto] cache aggiornata:", result); } else { console.warn("[admin scan foto] AdminCacheSync non disponibile"); } await readDBUser(); photoCacheSynced = true; } if (scanFinished && !queue) { if (window.operationBanner) { showPhotoBanner( "Scansione terminata; impossibile leggere lo stato della coda foto", { type: "error", sticky: true } ); } } } catch (err) { console.warn("[scan foto] aggiornamento stato fallito:", err.message); } finally { photoPollingBusy = false; } }, 1000); } /* ============================================================ ATTIVITÀ — SCANSIONE ED ELABORAZIONE CODA ============================================================ */ let activityScanRequestDone = false; let activityCacheSynced = false; let activityPollingBusy = false; async function scanActivities() { activityScanRequestDone = false; activityCacheSynced = false; if (window.operationBanner) { showActivityBanner( "Avvio scansione attività…", { type: "info", busy: true, sticky: true } ); } startScanActivitiesPolling(); try { await apiGet("/scanActivities"); activityScanRequestDone = true; } catch (err) { activityScanRequestDone = true; console.error("Errore avvio scan attività:", err); outActivities?.({ error: err.message }); stopActivityScanPolling(); if (window.operationBanner) { showActivityBanner( `Errore nella scansione attività: ${err.message}`, { type: "error", sticky: true } ); } } } function startScanActivitiesPolling() { stopActivityScanPolling(); activityScanInterval = setInterval(async () => { if (activityPollingBusy) return; activityPollingBusy = true; try { const data = await apiGet( `/scanStatus/activities?ts=${Date.now()}` ); const { current, total, percent } = updateScanUi(data); if (window.operationBanner) { window.operationBanner.setProgress("activities", percent); } let queue = null; try { queue = await apiGet( `/scanStatus/worker-queues?ts=${Date.now()}` ); if (!queue?.ok) queue = null; } catch (err) { console.warn( "[scan attività] lettura coda fallita:", err.message ); } const pending = Number(queue?.activities || 0); const errors = Number(queue?.activitiesErrors || 0); const scanFinished = activityScanRequestDone && (total === 0 || current >= total); // Aggiorna la cache una sola volta, quando la scansione è finita. if (scanFinished && !activityCacheSynced) { if (window.AdminCacheSync?.progressiveSyncActivitiesCacheFromAdmin) { const result = await AdminCacheSync.progressiveSyncActivitiesCacheFromAdmin(); console.log("[admin scan attività] cache aggiornata:", result); } else { console.warn( "[admin scan attività] AdminCacheSync non disponibile" ); } await readActivitiesDBUser(); activityCacheSynced = true; } if (!window.operationBanner) { // Senza banner non serve aggiornare messaggi, ma il polling // continua comunque fino alla fine della coda. if (scanFinished && queue && pending === 0) { stopActivityScanPolling(); } return; } if (!scanFinished) { showActivityBanner( `Scansione attività: ${percent}%` + (queue ? ` · ${pending} in coda/elaborazione · ${errors} errori` : ""), { type: errors > 0 ? "error" : "info", busy: true, sticky: true } ); } else if (!queue) { // Non nascondere il banner se non possiamo verificare la coda. showActivityBanner( "Scansione attività terminata; attendo di poter verificare la coda", { type: "error", busy: true, sticky: true } ); } else if (pending > 0) { showActivityBanner( `Elaborazione attività in corso: ${pending} in coda/elaborazione` + (errors > 0 ? ` · ${errors} errori` : ""), { type: errors > 0 ? "error" : "info", busy: true, sticky: true } ); } else { // La scansione è terminata e non ci sono job pending/processing. stopActivityScanPolling(); showActivityBanner( errors > 0 ? `Scansione attività completata con ${errors} errori` : "Scansione attività completata", { type: errors > 0 ? "error" : "success", busy: false, sticky: errors > 0, duration: 5000 } ); } } catch (err) { console.warn( "[scan attività] aggiornamento stato fallito, riprovo:", err.message ); } finally { activityPollingBusy = false; } }, 1000); }