server_photo_s85js/watcher/watcher_logic.mjs
2026-08-11 08:45:04 +02:00

240 lines
4.8 KiB
JavaScript

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