cleanup-ignored-screenshots.js
1 #!/usr/bin/env node 2 3 /** 4 * Cleanup Screenshots for Ignored Sites 5 * 6 * Deletes screenshot folders for sites with status='ignored' to free disk space. 7 * These sites are filtered out (directories, social media, franchises) and don't need screenshots. 8 */ 9 10 import Database from 'better-sqlite3'; 11 import { readdir, rm, stat } from 'fs/promises'; 12 import { join } from 'path'; 13 import { fileURLToPath } from 'url'; 14 import { dirname } from 'path'; 15 16 const __filename = fileURLToPath(import.meta.url); 17 const __dirname = dirname(__filename); 18 19 const DB_PATH = process.env.DATABASE_PATH || join(__dirname, '..', 'db', 'sites.db'); 20 const SCREENSHOTS_DIR = join(__dirname, '..', 'screenshots'); 21 22 // Parse command line args 23 const dryRun = process.argv.includes('--dry-run'); 24 25 /** 26 * Format bytes to human readable format 27 */ 28 function formatBytes(bytes) { 29 if (bytes === 0) return '0 Bytes'; 30 const k = 1024; 31 const sizes = ['Bytes', 'KB', 'MB', 'GB']; 32 const i = Math.floor(Math.log(bytes) / Math.log(k)); 33 return `${Math.round((bytes / Math.pow(k, i)) * 100) / 100} ${sizes[i]}`; 34 } 35 36 /** 37 * Get directory size recursively 38 */ 39 async function getDirectorySize(dirPath) { 40 let totalSize = 0; 41 try { 42 const entries = await readdir(dirPath, { withFileTypes: true }); 43 for (const entry of entries) { 44 const entryPath = join(dirPath, entry.name); 45 if (entry.isDirectory()) { 46 totalSize += await getDirectorySize(entryPath); 47 } else { 48 const stats = await stat(entryPath); 49 totalSize += stats.size; 50 } 51 } 52 } catch (error) { 53 // Directory might not exist or be accessible 54 return 0; 55 } 56 return totalSize; 57 } 58 59 async function main() { 60 console.log('š Analyzing screenshot folders for ignored sites...\n'); 61 62 // Connect to database 63 const db = new Database(DB_PATH, { readonly: true }); 64 65 // Get all ignored site IDs 66 const ignoredSites = db.prepare('SELECT id FROM sites WHERE status = ?').all('ignored'); 67 console.log(`Found ${ignoredSites.length} ignored sites in database`); 68 69 // Get all screenshot folders 70 let screenshotFolders; 71 try { 72 screenshotFolders = await readdir(SCREENSHOTS_DIR); 73 } catch (error) { 74 console.error('ā Error reading screenshots directory:', error.message); 75 process.exit(1); 76 } 77 78 console.log(`Found ${screenshotFolders.length} screenshot folders\n`); 79 80 // Find folders that belong to ignored sites 81 const ignoredIds = new Set(ignoredSites.map(s => s.id.toString())); 82 const foldersToDelete = screenshotFolders.filter(folder => ignoredIds.has(folder)); 83 84 console.log(`Found ${foldersToDelete.length} screenshot folders to delete\n`); 85 86 if (foldersToDelete.length === 0) { 87 console.log('ā No screenshot folders to clean up!'); 88 db.close(); 89 return; 90 } 91 92 // Calculate total size to be freed 93 let totalSize = 0; 94 console.log('š Calculating space to be freed...'); 95 96 for (const folder of foldersToDelete) { 97 const folderPath = join(SCREENSHOTS_DIR, folder); 98 const size = await getDirectorySize(folderPath); 99 totalSize += size; 100 } 101 102 console.log(`\nš¾ Space to be freed: ${formatBytes(totalSize)}`); 103 console.log(`š Folders to delete: ${foldersToDelete.length}`); 104 105 if (dryRun) { 106 console.log('\nš DRY RUN MODE - No files will be deleted'); 107 console.log('\nSample folders that would be deleted:'); 108 foldersToDelete.slice(0, 10).forEach(folder => { 109 console.log(` - screenshots/${folder}`); 110 }); 111 if (foldersToDelete.length > 10) { 112 console.log(` ... and ${foldersToDelete.length - 10} more`); 113 } 114 console.log('\nRun without --dry-run to actually delete these folders'); 115 } else { 116 console.log('\nšļø Deleting screenshot folders...'); 117 118 let deleted = 0; 119 let errors = 0; 120 121 for (const folder of foldersToDelete) { 122 const folderPath = join(SCREENSHOTS_DIR, folder); 123 try { 124 await rm(folderPath, { recursive: true, force: true }); 125 deleted++; 126 if (deleted % 100 === 0) { 127 console.log(` Deleted ${deleted}/${foldersToDelete.length} folders...`); 128 } 129 } catch (error) { 130 errors++; 131 console.error(` ā Error deleting ${folder}: ${error.message}`); 132 } 133 } 134 135 console.log(`\nā Cleanup complete!`); 136 console.log(` Deleted: ${deleted} folders`); 137 console.log(` Errors: ${errors}`); 138 console.log(` Space freed: ${formatBytes(totalSize)}`); 139 } 140 141 db.close(); 142 } 143 144 main().catch(error => { 145 console.error('ā Fatal error:', error); 146 process.exit(1); 147 });