'use strict'; const fs = require('fs'); const copyWithProgress = async ({ source, destination, jobLog, updateProgress, label }) => { const sourceSize = fs.statSync(source).size; const sourceSizeMb = (sourceSize / 1024 / 1024).toFixed(1); const startMs = Date.now(); let copiedBytes = 0; let nextProgressPercent = 0; const report = (percent, message) => { const boundedPercent = Math.min(Math.max(percent, 0), 100); if (typeof updateProgress === 'function') { updateProgress({ progress: boundedPercent, jobLog: message, }); } if (typeof jobLog === 'function') jobLog(`${message}\n`); }; await new Promise((resolve, reject) => { const rd = fs.createReadStream(source); const wr = fs.createWriteStream(destination); let settled = false; const fail = (error) => { if (settled) return; settled = true; rd.destroy(); wr.destroy(); reject(error); }; rd.on('error', fail); wr.on('error', fail); wr.on('finish', () => { if (settled) return; settled = true; resolve(); }); rd.on('data', (chunk) => { copiedBytes += chunk.length; const percent = sourceSize === 0 ? 100 : Math.floor((copiedBytes / sourceSize) * 100); if (percent < nextProgressPercent) return; const elapsedSeconds = Math.max((Date.now() - startMs) / 1000, 0.1); const copiedMb = (copiedBytes / 1024 / 1024).toFixed(1); const speedMbPerSecond = (copiedBytes / 1024 / 1024 / elapsedSeconds).toFixed(1); report( percent, `[${label}] Progress: ${Math.min(percent, 100)}% ` + `(${copiedMb}/${sourceSizeMb} MB, ${speedMbPerSecond} MB/s)`, ); nextProgressPercent = (Math.floor(percent / 10) + 1) * 10; }); rd.pipe(wr); }); const destinationSize = fs.statSync(destination).size; if (destinationSize !== sourceSize) { throw new Error( `[${label}] Copy size mismatch: expected ${sourceSize} bytes, got ${destinationSize}`, ); } const elapsed = ((Date.now() - startMs) / 1000).toFixed(1); report(100, `[${label}] Done: ${sourceSizeMb} MB copied in ${elapsed}s`); }; module.exports = { copyWithProgress };