Certo. Dopo le modifiche in s179js, il flusso Activity è ormai molto simile a quello Photo, ma la notifica alla gallery segue ancora il percorso tramite POST /activities. 1. Flusso attuale di scansione Activity Quando premi Scan Activities: UTENTE │ ▼ Scan Activities │ ▼ routes/scanActivities.js │ ▼ scanActivitiesUser() │ trova cartelle Activity │ ▼ scanActivity() │ ▼ scanActivityCartella() │ trova FIT/GPX/TCX │ ▼ enqueueActivityIngestJob() │ ▼ activity_ingest_jobs │ ┌─────────────┼──────────────┐ │ │ │ pending processing done │ ▼ │ ACTIVITY DAEMON [A] └────────────►│ ▼ runOneJob() │ ▼ scanActivitySingle() │ ▼ processActivityFile() │ parsing FIT/GPX/TCX │ ▼ activities DB │ ├───────────────┐ │ │ ▼ ▼ geocode_jobs ritorno meta │ ▼ GEOCODE DAEMON [G] │ ▼ geocoding Lo Scan adesso è solo il produttore Questa è la differenza fondamentale rispetto a prima. Prima: Scan ↓ trova file ↓ parsa FIT ↓ salva Activity ↓ file successivo Adesso: Scan ↓ trova file ↓ mette job pending ↓ file successivo Per questo nel log vedi velocemente: [S] 📥 [ACTIVITY QUEUE] xxx.fit status=pending inserted [S] 📥 [ACTIVITY QUEUE] yyy.fit status=pending inserted [S] 📥 [ACTIVITY QUEUE] zzz.fit status=pending inserted e poi: [S] Scan ATTIVITÀ COMPLETATO Questo però non significa che tutte le Activity siano elaborate. Significa solamente che la fase di discovery ha finito di riempire la coda. 2. Il daemon svuota la coda Parallelamente [A] fa: activity_ingest_jobs │ ▼ prende primo pending │ ▼ processing │ ▼ parse FIT │ ▼ salva Activity │ ▼ accoda Geocode │ ▼ sincronizza server │ ▼ done │ ▼ prende IMMEDIATAMENTE il pending successivo Infatti nel tuo log: [A] Avvio job ... [A] Nuova activity ... [A] Activity ... inviata al server remoto [A] Job ... completato; stato: done [A] Avvio job successivo ... Non ci sono 2 secondi tra un'attività e l'altra. I 2 secondi servono solo quando: pending = 0 3. Geocode è completamente indipendente Quando scanActivitySingle() salva l'Activity esegue anche: enqueueGeocodeJob quindi: ACTIVITY DAEMON │ ▼ activities DB │ └────────────► geocode_jobs │ ▼ GEOCODE DAEMON [G] │ ▼ geocoding Activity daemon non aspetta che il Geocode finisca. Per questo nel log puoi vedere: [A] job Activity successivo mentre: [G] geocodeWorker processing sta ancora lavorando sull'attività precedente. 4. Flusso attuale di notifica alla gallery Activity Qui il percorso è leggermente diverso da Photo. Quando scanActivitySingle() restituisce la nuova Activity: ACTIVITY DAEMON [A] │ ▼ scannedActivity/meta │ ▼ postWithAuth() │ ▼ POST BASE_URL/activities │ ▼ routes/activities.js │ ▼ INSERT activity ON CONFLICT MERGE │ ▼ invalidateActivitySpatialIndex() │ ▼ broadcastToUser() │ payload: type: "added" id: activityId entity: "activity" │ ▼ WebSocket │ ▼ CLIENT │ ▼ Activity Gallery Quindi la notifica alla gallery non viene inviata direttamente dall'Activity daemon. Il daemon fa: Activity daemon ↓ HTTP POST ↓ server ↓ WebSocket ↓ Gallery Questo è importante perché il processo [A] non importa ws-server e quindi non rischia di aprire un secondo WebSocket sulla porta 4002. 5. Pipeline completa Mettendo insieme tutto: SCAN │ ▼ trova FIT/GPX/TCX │ ▼ activity_ingest_jobs │ │ pending ▼ ACTIVITY DAEMON [A] │ ▼ parse FIT/GPX/TCX │ ▼ activities DB │ ┌────────────┴─────────────┐ │ │ ▼ ▼ geocode_jobs activity meta │ │ ▼ ▼ GEOCODE DAEMON [G] POST /activities │ ▼ SERVER │ INSERT / UPDATE │ ▼ WebSocket │ ▼ ACTIVITY GALLERY Activity daemon │ ▼ job status = done │ ▼ prossimo pending 6. Cosa significa per il futuro banner Ora abbiamo finalmente una coda affidabile: activity_ingest_jobs quindi il banner Activity non deve più basarsi sul vecchio: scan_status.json Può mostrare direttamente: remaining = pending + processing Per esempio: Activities processing: 27 poi: Activities processing: 26 Activities processing: 25 ... Activities processing: 1 e quando: pending = 0 processing = 0 il banner scompare. Questo sarebbe molto più corretto del vecchio progress dello Scan, perché lo Scan può aver finito di accodare i file mentre l'Activity daemon ha ancora 30 FIT da elaborare.