150 lines
4.1 KiB
JavaScript
Executable File
150 lines
4.1 KiB
JavaScript
Executable File
#!/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<string, string[]>;
|
|
duplicatesInBackup: Record<string, string[]>;
|
|
}
|
|
|
|
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<string> {
|
|
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);
|
|
});
|
|
|