/ scripts / cleanup-ignored-screenshots.js
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  });