import fs from 'fs/promises'; import path from 'path'; import sharp from 'sharp'; import { createConnection } from './database.js'; class PictureSyncer { constructor() { if (PictureSyncer.instance) { return PictureSyncer.instance; } this.cacheBaseDir = process.env.CACHE_LOCATION || '.'; PictureSyncer.instance = this; } async syncImages(imageIds, groupName) { const groupDir = path.join(this.cacheBaseDir, 'img', groupName); // Ensure directory exists await fs.mkdir(groupDir, { recursive: true }); // Get existing files let existingFiles = []; try { existingFiles = await fs.readdir(groupDir); } catch (err) { // Directory might be empty or new } // Filter for image files (assuming we save as {id}.avif) const existingIds = existingFiles .filter(f => f.endsWith('.avif')) .map(f => parseInt(f.replace('.avif', ''))); const validIds = new Set(imageIds.filter(id => id !== null && id !== undefined)); // 1. Delete obsolete images const toDelete = existingIds.filter(id => !validIds.has(id)); for (const id of toDelete) { const filePath = path.join(groupDir, `${id}.avif`); await fs.unlink(filePath); } if (toDelete.length > 0) { console.log(`🗑️ Deleted ${toDelete.length} obsolete images.`); } // 2. Download missing images const toDownload = imageIds.filter(id => id !== null && id !== undefined && !existingIds.includes(id)); if (toDownload.length > 0) { console.log(`📥 Downloading ${toDownload.length} new images for group '${groupName}'...`); await this._downloadImages(toDownload, groupDir); } else { console.log(`✅ No new images to download for group '${groupName}'.`); } } async _downloadImages(ids, dir) { let pool; try { pool = await createConnection(); // Process in chunks to avoid huge queries const chunkSize = 50; for (let i = 0; i < ids.length; i += chunkSize) { const chunk = ids.slice(i, i + chunkSize); const list = chunk.join(','); const result = await pool.request().query(` SELECT kBild, bBild FROM tBild WHERE kBild IN (${list}) `); for (const record of result.recordset) { if (record.bBild) { const filePath = path.join(dir, `${record.kBild}.avif`); // Convert to AVIF using sharp await sharp(record.bBild) .avif({ quality: 80 }) .toFile(filePath); } } const processed = Math.min(i + chunkSize, ids.length); if (processed === ids.length) { console.log(`✅ Processed ${processed}/${ids.length} images.`); } else { console.log(`⏳ Processed ${processed}/${ids.length} images...`); } } } catch (err) { console.error('❌ Error downloading images:', err); } finally { if (pool) { await pool.close(); } } } } const instance = new PictureSyncer(); export default instance;