import "dotenv/config"; import { createClient } from "webdav"; import { mkdir, writeFile, stat, readdir, rm } from "fs/promises"; import { join, dirname, relative } from "path"; interface FileStat { filename: string; basename: string; type: "file" | "directory"; size: number; lastmod: string; } const WEBDAV_URL = process.env.WEBDAV_URL || "https://nas.arfaoui.net:6006"; const WEBDAV_PATH = process.env.WEBDAV_PATH || "/photo/Portfolio"; const WEBDAV_USER = process.env.WEBDAV_USER; const WEBDAV_PASS = process.env.WEBDAV_PASS; const DEST_DIR = "src/assets/images/photos"; async function main() { if (!WEBDAV_USER || !WEBDAV_PASS) { console.error("Error: WEBDAV_USER and WEBDAV_PASS environment variables are required"); process.exit(1); } const client = createClient(WEBDAV_URL, { username: WEBDAV_USER, password: WEBDAV_PASS, }); console.log(`Fetching images from ${WEBDAV_URL}${WEBDAV_PATH}...`); // Collecter tous les fichiers/dossiers distants const remoteItems = new Set(); await syncDirectory(client, WEBDAV_PATH, DEST_DIR, remoteItems); // Supprimer les fichiers locaux qui n'existent plus sur le NAS await cleanupLocalFiles(DEST_DIR, remoteItems); console.log("Done!"); } async function syncDirectory( client: ReturnType, remotePath: string, localPath: string, remoteItems: Set ) { await mkdir(localPath, { recursive: true }); remoteItems.add(localPath); const items = (await client.getDirectoryContents(remotePath)) as FileStat[]; for (const item of items) { const localItemPath = join(localPath, item.basename); const remoteItemPath = item.filename; if (item.type === "directory") { console.log(` [dir] ${item.basename}/`); await syncDirectory(client, remoteItemPath, localItemPath, remoteItems); } else if (item.type === "file" && /\.(jpg|jpeg|png|webp)$/i.test(item.basename)) { remoteItems.add(localItemPath); const needsDownload = await shouldDownload(localItemPath, item); if (needsDownload) { console.log(` [download] ${remoteItemPath}`); const content = (await client.getFileContents(remoteItemPath)) as Buffer; await mkdir(dirname(localItemPath), { recursive: true }); await writeFile(localItemPath, content); } else { console.log(` [skip] ${item.basename} (unchanged)`); } } } } async function shouldDownload(localPath: string, remoteItem: FileStat): Promise { try { const localStat = await stat(localPath); const remoteSize = remoteItem.size; const localSize = localStat.size; if (remoteSize !== localSize) { return true; } const remoteDate = new Date(remoteItem.lastmod).getTime(); const localDate = localStat.mtime.getTime(); return remoteDate > localDate; } catch { return true; } } async function cleanupLocalFiles(localDir: string, remoteItems: Set) { const localFiles = await collectLocalFiles(localDir); let deletedCount = 0; for (const localFile of localFiles) { if (!remoteItems.has(localFile)) { const relativePath = relative(localDir, localFile); console.log(` [delete] ${relativePath}`); await rm(localFile, { recursive: true, force: true }); deletedCount++; } } if (deletedCount > 0) { console.log(`Deleted ${deletedCount} orphaned file(s)/folder(s)`); // Nettoyer les dossiers vides await cleanupEmptyDirs(localDir); } } async function collectLocalFiles(dir: string): Promise { const files: string[] = []; try { const entries = await readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = join(dir, entry.name); if (entry.isDirectory()) { files.push(fullPath); const subFiles = await collectLocalFiles(fullPath); files.push(...subFiles); } else if (/\.(jpg|jpeg|png|webp)$/i.test(entry.name)) { files.push(fullPath); } } } catch { // Dossier n'existe pas encore } return files; } async function cleanupEmptyDirs(dir: string) { try { const entries = await readdir(dir, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory()) { const subDir = join(dir, entry.name); await cleanupEmptyDirs(subDir); // Vérifier si le dossier est vide après nettoyage récursif const subEntries = await readdir(subDir); if (subEntries.length === 0) { console.log(` [delete] ${relative(DEST_DIR, subDir)}/ (empty)`); await rm(subDir, { recursive: true }); } } } } catch { // Ignore errors } } main().catch((err) => { console.error("Error:", err.message); process.exit(1); });