gallery_server_client_s190js/public/js/admin/scan.js
2026-10-26 11:00:14 +01:00

331 lines
9.7 KiB
JavaScript

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);
}