Files
Tdarr-plugin/FlowHelpers/1.0.0/copyWithProgress.js
T
PickleRick 7ee5a5bc48
Build / Lint & Test (push) Successful in 28s
Save uncommitted changes
2026-09-21 23:38:34 +02:00

111 lines
3.4 KiB
JavaScript

'use strict';
const fs = require('fs');
const MAX_RETRIES = 2;
const RETRY_DELAY_MS = 1000;
const PROGRESS_INTERVAL_MS = 250;
const sleep = (milliseconds) => new Promise((resolve) => {
setTimeout(resolve, milliseconds);
});
const copyWithProgress = async ({ source, destination, jobLog, updateProgress, label }) => {
const sourceSize = fs.statSync(source).size;
const sourceSizeMb = sourceSize / 1024 / 1024;
let lastError;
const report = (progress, message) => {
if (typeof updateProgress === 'function') {
updateProgress({ progress, jobLog: message });
}
if (typeof jobLog === 'function') jobLog(`${message}\n`);
};
for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {
if (attempt > 0) {
report(0, `[${label}] Retrying copy (${attempt}/${MAX_RETRIES})...`);
await sleep(RETRY_DELAY_MS);
}
try {
if (fs.existsSync(destination)) fs.unlinkSync(destination);
const startedAt = Date.now();
let lastReportedPercent = -1;
let previousBytes = 0;
let previousAt = startedAt;
let copying = true;
const progressTimer = setInterval(() => {
if (!copying || !fs.existsSync(destination)) return;
let copiedBytes;
try {
copiedBytes = fs.statSync(destination).size;
} catch (error) {
if (error.code === 'ENOENT') return;
return;
}
const percent = sourceSize === 0
? 99
: Math.min(Math.floor((copiedBytes / sourceSize) * 100), 99);
if (percent <= lastReportedPercent) return;
const now = Date.now();
const intervalSeconds = Math.max((now - previousAt) / 1000, 0.001);
const intervalSpeed = (copiedBytes - previousBytes) / 1024 / 1024 / intervalSeconds;
report(
percent,
`[${label}] Progress: ${percent}% `
+ `(${(copiedBytes / 1024 / 1024).toFixed(1)}/${sourceSizeMb.toFixed(1)} MB, `
+ `${intervalSpeed.toFixed(1)} MB/s)`,
);
lastReportedPercent = percent;
previousBytes = copiedBytes;
previousAt = now;
}, PROGRESS_INTERVAL_MS);
try {
await fs.promises.copyFile(source, destination);
} finally {
copying = false;
clearInterval(progressTimer);
}
const destinationSize = fs.statSync(destination).size;
if (destinationSize !== sourceSize) {
throw new Error(
`Copy size mismatch: expected ${sourceSize} bytes, got ${destinationSize}`,
);
}
const elapsedSeconds = Math.max((Date.now() - startedAt) / 1000, 0.001);
const averageSpeed = sourceSize / 1024 / 1024 / elapsedSeconds;
report(
100,
`[${label}] Done: ${sourceSizeMb.toFixed(1)} MB copied in `
+ `${elapsedSeconds.toFixed(1)}s (${averageSpeed.toFixed(1)} MB/s average)`,
);
return;
} catch (error) {
lastError = error;
try {
if (fs.existsSync(destination)) fs.unlinkSync(destination);
} catch (cleanupError) {
lastError = new Error(
`${error.message}; could not remove partial copy: ${cleanupError.message}`,
);
}
if (attempt < MAX_RETRIES && typeof jobLog === 'function') {
jobLog(`[${label}] Copy attempt ${attempt + 1} failed: ${lastError.message}\n`);
}
}
}
throw new Error(
`[${label}] Copy failed after ${MAX_RETRIES + 1} attempts: ${lastError.message}`,
);
};
module.exports = { copyWithProgress };