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 match = fullPath.match( /^\/app\/(photos|activities)\/([^/]+)\/original(?:\/|$)/ ); if (!match) { return null; } return { area: match[1], user: 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 = path.replace(/^\/app/, ""); const headers = { "Content-Type": "application/json", Authorization: `Bearer ${WATCHER_TOKEN}` }; const payload = { type, file, path: relPath, user }; console.log("AUTO_SCAN:", payload); 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}` ); } return res.ok; } // =============================== // 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 }); const info = parsePath(path); console.log("PARSE_PATH:", info); if (!info) { console.log("PATH IGNORATO"); return; } const { user, area } = info; const isDir = action.includes("ISDIR"); if (!isDir && !isAllowedFile(area, file)) { console.log( `FILE IGNORATO area=${area} file=${file}` ); return; } 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; } console.log( `[${area}] ${user} -> ${type} ${file}` ); await callAutoScan( type, file, path, user ); } catch (err) { console.error( "Errore watcher:", err ); } }); } startWatcher();