diff --git a/scripts/fetch-images.ts b/scripts/fetch-images.ts index a0ebdaa..491caa5 100644 --- a/scripts/fetch-images.ts +++ b/scripts/fetch-images.ts @@ -1,7 +1,7 @@ import "dotenv/config"; import { createClient } from "webdav"; -import { mkdir, writeFile, stat } from "fs/promises"; -import { join, dirname } from "path"; +import { mkdir, writeFile, stat, readdir, rm } from "fs/promises"; +import { join, dirname, relative } from "path"; interface FileStat { filename: string; @@ -30,13 +30,25 @@ async function main() { console.log(`Fetching images from ${WEBDAV_URL}${WEBDAV_PATH}...`); - await syncDirectory(client, WEBDAV_PATH, DEST_DIR); + // 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) { +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[]; @@ -46,8 +58,9 @@ async function syncDirectory(client: ReturnType, remotePath if (item.type === "directory") { console.log(` [dir] ${item.basename}/`); - await syncDirectory(client, remoteItemPath, localItemPath); + 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) { @@ -81,6 +94,72 @@ async function shouldDownload(localPath: string, remoteItem: FileStat): Promise< } } +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);