#!/usr/bin/env node import { execSync } from 'child_process'; import { existsSync, mkdirSync } from 'fs'; import { readFileSync } from 'fs'; import { basename, dirname, join } from 'path'; interface FileInfo { path: string; size: number; hash?: string; } interface Results { matched: number; missingInBackup: FileInfo[]; missingOnPhone: FileInfo[]; duplicatesOnPhone: Record; duplicatesInBackup: Record; } function getUniqueFilename(targetPath: string): string { if (!existsSync(targetPath)) { return targetPath; } const dir = dirname(targetPath); const ext = targetPath.match(/\.[^.]+$/)?.[0] || ''; const nameWithoutExt = basename(targetPath, ext); let counter = 1; let newPath: string; do { newPath = join(dir, `${nameWithoutExt}-${counter}${ext}`); counter++; } while (existsSync(newPath)); return newPath; } async function pullFile(androidPath: string, localDir: string): Promise { const filename = basename(androidPath); const targetPath = join(localDir, filename); const finalPath = getUniqueFilename(targetPath); console.log(`šŸ“„ Pulling: ${androidPath}`); console.log(` -> ${finalPath}`); try { execSync(`adb pull "${androidPath}" "${finalPath}"`, { encoding: 'utf-8', stdio: 'pipe' }); return finalPath; } catch (err: any) { console.error(`āŒ Failed to pull: ${androidPath}`); console.error(` Error: ${err.message}`); throw err; } } async function main() { const resultsPath = 'results.json'; if (!existsSync(resultsPath)) { console.error(`āŒ Error: ${resultsPath} not found`); console.error('Run photos-diff.ts first to generate results.json'); process.exit(1); } console.log('šŸ“– Reading results.json...'); const results: Results = JSON.parse(readFileSync(resultsPath, 'utf-8')); const missingFiles = results.missingInBackup; if (missingFiles.length === 0) { console.log('āœ… No missing files! Backup is complete.'); return; } console.log(`\nšŸ” Found ${missingFiles.length} missing files`); console.log(`šŸ“¦ Total size: ${(missingFiles.reduce((sum, f) => sum + f.size, 0) / 1024 / 1024).toFixed(2)} MB`); // Create target directory if needed const targetDir = './pulled-files'; if (!existsSync(targetDir)) { console.log(`\nšŸ“ Creating directory: ${targetDir}`); mkdirSync(targetDir, { recursive: true }); } console.log(`\nšŸš€ Starting download to: ${targetDir}\n`); const startTime = Date.now(); const pulled: string[] = []; const failed: string[] = []; for (let i = 0; i < missingFiles.length; i++) { const file = missingFiles[i]; try { const localPath = await pullFile(file.path, targetDir); pulled.push(localPath); } catch (err) { failed.push(file.path); } // Progress const processed = i + 1; const elapsed = Date.now() - startTime; const avgTime = elapsed / processed; const remaining = missingFiles.length - processed; const eta = Math.round((avgTime * remaining) / 1000); const etaStr = eta > 60 ? `${Math.floor(eta / 60)}m ${eta % 60}s` : `${eta}s`; console.log(`\nšŸ“Š Progress: ${processed}/${missingFiles.length} | ETA: ${etaStr}`); console.log(` āœ… Success: ${pulled.length} | āŒ Failed: ${failed.length}\n`); } // Final report const totalTime = Math.round((Date.now() - startTime) / 1000); const timeStr = totalTime > 60 ? `${Math.floor(totalTime / 60)}m ${totalTime % 60}s` : `${totalTime}s`; console.log('='.repeat(60)); console.log('šŸ“Š DOWNLOAD COMPLETE'); console.log('='.repeat(60)); console.log(`āœ… Successfully pulled: ${pulled.length} files`); console.log(`āŒ Failed: ${failed.length} files`); console.log(`ā±ļø Total time: ${timeStr}`); console.log(`šŸ“ Files saved to: ${targetDir}`); console.log('='.repeat(60)); if (failed.length > 0) { console.log('\nāŒ Failed files:'); failed.forEach(f => console.log(` - ${f}`)); process.exit(1); } } main().catch(err => { console.error('šŸ’„ Fatal error:', err); process.exit(1); });