import readline from "readline"; import dotenv from "dotenv"; dotenv.config(); // =============================== // CONFIG // =============================== const BASE_URL = process.env.BASE_URL; if (!BASE_URL) { console.error("ERRORE: BASE_URL non definita nel .env"); process.exit(1); } const WATCHER_TOKEN = process.env.WATCHER_TOKEN; if (!WATCHER_TOKEN) { console.error("ERRORE: WATCHER_TOKEN non definito nel .env"); process.exit(1); } console.log("Watcher globale avviato"); // =============================== // STDIN // =============================== const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity }); // =============================== // ESTENSIONI FOTO / VIDEO // =============================== const photoExt = [ ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff", ".heic", ".heif", ".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v" ]; // =============================== // ESTENSIONI ATTIVITÀ // =============================== const activityExt = [ ".fit", ".gpx", ".tcx" ]; // =============================== // PARSING PATH // =============================== function parsePath(fullPath) { const normalized = fullPath.replace(/\\/g, "/"); /* * Supporta sia: * * /app/photos/Fabio/original/... * /app/activities/Fabio/original/... * * sia: * * ../public/photos/Fabio/original/... * ../public/activities/Fabio/original/... * */ const match = normalized.match( /(?:^|\/)(photos|activities)\/([^/]+)\/original(?:\/|$)/ ); if (!match) { return null; } return { area: match[1], user: match[2] }; } // =============================== // NORMALIZZAZIONE PATH // =============================== function normalizePath(fullPath) { const normalized = fullPath.replace(/\\/g, "/"); /* * Trasforma: * * ../public/photos/Fabio/original/traptour/ * * in: * * /photos/Fabio/original/traptour/ * * * e anche: * * /app/photos/Fabio/original/traptour/ * * in: * * /photos/Fabio/original/traptour/ */ const match = normalized.match( /(?:^|\/)(photos|activities)\/(.+)$/ ); if (!match) { return null; } return `/${match[1]}/${match[2]}`; } // =============================== // FILTRO FILE // =============================== function isAllowedFile(area, filename) { const lower = filename.toLowerCase(); if (area === "photos") { return photoExt.some(ext => lower.endsWith(ext)); } if (area === "activities") { return activityExt.some(ext => lower.endsWith(ext)); } return false; } // =============================== // CHIAMATA API // =============================== async function callAutoScan(type, file, path, user) { const relPath = normalizePath(path); if (!relPath) { console.error( "Impossibile normalizzare path:", path ); return false; } const headers = { "Content-Type": "application/json", Authorization: `Bearer ${WATCHER_TOKEN}` }; const payload = { type, file, path: relPath, user }; console.log("AUTO_SCAN:", payload); try { const res = await fetch( `${BASE_URL}/api_v1/auto_scan`, { method: "POST", headers, body: JSON.stringify(payload) } ); if (!res.ok) { console.error( `Errore auto_scan ${res.status}` ); try { const text = await res.text(); if (text) { console.error( "Risposta API:", text ); } } catch (_) { // niente } return false; } console.log( `AUTO_SCAN OK ${res.status}` ); return true; } catch (err) { console.error( "Errore chiamata auto_scan:", err ); return false; } } // =============================== // WATCHER // =============================== function startWatcher() { rl.on("line", async line => { console.log("RAW:", line); try { const parts = line.trim().split(/\s+/); if (parts.length < 3) { console.log( "EVENTO SCARTATO: formato non valido" ); return; } const path = parts[0]; const action = parts[1]; const file = parts.slice(2).join(" "); console.log({ path, action, file }); // =============================== // PARSE PATH // =============================== const info = parsePath(path); console.log( "PARSE_PATH:", info ); if (!info) { console.log( "PATH IGNORATO" ); return; } const { user, area } = info; // =============================== // DIRECTORY? // =============================== const isDir = action.includes("ISDIR"); // =============================== // FILTRO ESTENSIONE // =============================== if ( !isDir && !isAllowedFile(area, file) ) { console.log( `FILE IGNORATO area=${area} file=${file}` ); return; } // =============================== // TIPO EVENTO // =============================== let type = null; if ( action === "MOVED_TO,ISDIR" ) { type = "ADD_DIR"; } else if ( action === "MOVED_FROM,ISDIR" ) { type = "DEL_DIR"; } else if ( /^CLOSE_WRITE(,CLOSE)?$/.test(action) ) { type = "ADD"; } else if ( action === "MOVED_TO" ) { type = "ADD"; } else if ( action === "MOVED_FROM" ) { type = "DEL"; } else if ( action === "DELETE" ) { type = "DEL"; } else { console.log( `AZIONE IGNORATA: ${action}` ); return; } // =============================== // EVENTO // =============================== console.log( `[${area}] ${user} -> ${type} ${file}` ); // =============================== // API // =============================== await callAutoScan( type, file, path, user ); } catch (err) { console.error( "Errore watcher:", err ); } }); } // =============================== // START // =============================== startWatcher();