Save uncommitted changes
Build / Lint & Test (push) Successful in 18s

This commit is contained in:
PickleRick committed 2026-09-21 12:34:15 +02:00
1 parent a8c4a81141
commit 5d43ba6a26
8 files changed
+430 -20

No files matched your search

@@ -0,0 +1,70 @@
'use strict';
const path = require('path');
const fs = require('fs');
const { copyWithProgress } = require('../../../../FlowHelpers/1.0.0/copyWithProgress');
const details = () => ({
name: 'Copy Source to Work Dir (Progress)',
description:
'Copies the source file into the worker local workDir using a streamed, verified copy '
+ 'with progress updates. Use before transcode plugins for files on network shares.',
style: { borderColor: '#607D8B' },
tags: 'utility,copy,progress',
isStartPlugin: false,
pType: '',
requiresVersion: '2.11.01',
sidebarPosition: 0,
icon: 'faCopy',
inputs: [
{
label: 'Skip if already local',
name: 'skipIfLocal',
type: 'boolean',
defaultValue: 'true',
inputUI: { type: 'dropdown', options: ['true', 'false'] },
tooltip: 'Skip when the source is already inside workDir.',
},
],
outputs: [{ number: 1, tooltip: 'File is ready in workDir — continue to next plugin' }],
});
const plugin = async (args) => {
const { jobLog, inputFileObj, variables, workDir } = args;
const inputFile = inputFileObj._id;
const ext = path.extname(inputFile);
const baseName = path.basename(inputFile, ext);
const localCopy = path.join(workDir, `${baseName}_old${ext}`);
const skipIfLocal = (args.inputs.skipIfLocal || 'true') === 'true';
const relativeInput = path.relative(path.resolve(workDir), path.resolve(inputFile));
if (skipIfLocal && relativeInput !== '..' && !relativeInput.startsWith(`..${path.sep}`)
&& !path.isAbsolute(relativeInput)) {
jobLog('[copyToWorkDir] Source file is already inside workDir — skipping copy.\n');
return { outputNumber: 1, outputFileObj: inputFileObj, variables };
}
if (!fs.existsSync(inputFile)) throw new Error(`[copyToWorkDir] Source file not found: ${inputFile}`);
variables.originalLibraryFile = variables.originalLibraryFile || inputFile;
variables.originalLibraryDir = variables.originalLibraryDir || path.dirname(inputFile);
variables.workDirSourceBaseName = baseName;
variables.workDirCopyApplied = 'true';
jobLog(`[copyToWorkDir] Copying ${inputFile} to ${localCopy}\n`);
await copyWithProgress({
source: inputFile,
destination: localCopy,
jobLog,
updateProgress: args.updateProgress,
label: 'copyToWorkDir',
});
return {
outputNumber: 1,
outputFileObj: { ...inputFileObj, _id: localCopy },
variables,
};
};
module.exports.details = details;
module.exports.plugin = plugin;
@@ -0,0 +1,114 @@
'use strict';
const path = require('path');
const fs = require('fs');
const details = () => ({
name: 'Replace Original File',
description:
'Copies the processed file to the original library path, verifies the copy, '
+ 'and atomically replaces the original with rollback if replacement fails.',
style: { borderColor: '#FF9800' },
tags: 'utility,replace',
isStartPlugin: false,
pType: '',
requiresVersion: '2.11.01',
sidebarPosition: 1,
icon: 'faExchangeAlt',
inputs: [
{
label: 'Delete processed work file',
name: 'deleteSource',
type: 'boolean',
defaultValue: 'true',
inputUI: { type: 'dropdown', options: ['true', 'false'] },
tooltip: 'Delete the processed file after it has replaced the original.',
},
],
outputs: [{ number: 1, tooltip: 'Original file replaced — continue to next plugin' }],
});
const copyAndVerify = (source, destination) => {
fs.copyFileSync(source, destination);
const sourceSize = fs.statSync(source).size;
const destinationSize = fs.statSync(destination).size;
if (sourceSize !== destinationSize) {
throw new Error(
`Copy size mismatch: expected ${sourceSize} bytes, got ${destinationSize}`,
);
}
};
const plugin = async (args) => {
const { inputFileObj, variables, jobLog } = args;
const source = inputFileObj._id;
const configuredDestination = variables.originalLibraryFile || args.originalLibraryFile;
const destination = typeof configuredDestination === 'string'
? configuredDestination
: configuredDestination && configuredDestination._id;
if (!destination || typeof destination !== 'string') {
throw new Error('[replaceOriginalFile] No original library path is available');
}
if (!fs.existsSync(source)) {
throw new Error(`[replaceOriginalFile] Processed file not found: ${source}`);
}
if (path.resolve(source) === path.resolve(destination)) {
jobLog('[replaceOriginalFile] Processed file is already at the original path — skipping.\n');
return { outputNumber: 1, outputFileObj: inputFileObj, variables };
}
const destinationDir = path.dirname(destination);
if (!fs.existsSync(destinationDir)) {
throw new Error(`[replaceOriginalFile] Destination directory does not exist: ${destinationDir}`);
}
const temporary = `${destination}.tdarr.tmp`;
const backup = `${destination}.partial.old`;
let backupCreated = false;
let installed = false;
try {
if (fs.existsSync(temporary)) fs.unlinkSync(temporary);
copyAndVerify(source, temporary);
if (fs.existsSync(destination)) {
if (fs.existsSync(backup)) fs.unlinkSync(backup);
fs.renameSync(destination, backup);
backupCreated = true;
}
fs.renameSync(temporary, destination);
installed = true;
if (backupCreated) {
try {
fs.unlinkSync(backup);
} catch (error) {
jobLog(`[replaceOriginalFile] WARNING: Could not remove backup ${backup}: ${error.message}\n`);
}
}
if ((args.inputs.deleteSource || 'true') === 'true') {
try {
fs.unlinkSync(source);
} catch (error) {
jobLog(`[replaceOriginalFile] WARNING: Could not delete processed file ${source}: ${error.message}\n`);
}
}
jobLog(`[replaceOriginalFile] Replaced original file: ${destination}\n`);
return {
outputNumber: 1,
outputFileObj: { ...inputFileObj, _id: destination },
variables,
};
} catch (error) {
if (fs.existsSync(temporary)) fs.unlinkSync(temporary);
if (backupCreated && !installed && !fs.existsSync(destination)) {
fs.renameSync(backup, destination);
}
throw new Error(`[replaceOriginalFile] Replacement failed: ${error.message}`);
}
};
module.exports.details = details;
module.exports.plugin = plugin;
@@ -0,0 +1,99 @@
'use strict';
const path = require('path');
const fs = require('fs');
const { copyWithProgress } = require('../../../../FlowHelpers/1.0.0/copyWithProgress');
const details = () => ({
name: 'Replace Original File (Progress)',
description:
'Copies the processed file to the original library path with streamed progress, '
+ 'size verification, an atomic rename, and rollback if replacement fails.',
style: { borderColor: '#FF9800' },
tags: 'utility,replace,progress',
isStartPlugin: false,
pType: '',
requiresVersion: '2.11.01',
sidebarPosition: 1,
icon: 'faExchangeAlt',
inputs: [
{
label: 'Delete processed work file',
name: 'deleteSource',
type: 'boolean',
defaultValue: 'true',
inputUI: { type: 'dropdown', options: ['true', 'false'] },
tooltip: 'Delete the processed file after it has replaced the original.',
},
],
outputs: [{ number: 1, tooltip: 'Original file replaced — continue to next plugin' }],
});
const plugin = async (args) => {
const { inputFileObj, variables, jobLog } = args;
const source = inputFileObj._id;
const configuredDestination = variables.originalLibraryFile || args.originalLibraryFile;
const destination = typeof configuredDestination === 'string'
? configuredDestination
: configuredDestination && configuredDestination._id;
if (!destination || typeof destination !== 'string') {
throw new Error('[replaceOriginalFile] No original library path is available');
}
if (!fs.existsSync(source)) throw new Error(`[replaceOriginalFile] Processed file not found: ${source}`);
if (path.resolve(source) === path.resolve(destination)) {
jobLog('[replaceOriginalFile] Processed file is already at the original path — skipping.\n');
return { outputNumber: 1, outputFileObj: inputFileObj, variables };
}
const destinationDir = path.dirname(destination);
if (!fs.existsSync(destinationDir)) {
throw new Error(`[replaceOriginalFile] Destination directory does not exist: ${destinationDir}`);
}
const temporary = `${destination}.tdarr.tmp`;
const backup = `${destination}.partial.old`;
let backupCreated = false;
let installed = false;
try {
if (fs.existsSync(temporary)) fs.unlinkSync(temporary);
await copyWithProgress({
source,
destination: temporary,
jobLog,
updateProgress: args.updateProgress,
label: 'replaceOriginalFile',
});
if (fs.existsSync(destination)) {
if (fs.existsSync(backup)) fs.unlinkSync(backup);
fs.renameSync(destination, backup);
backupCreated = true;
}
fs.renameSync(temporary, destination);
installed = true;
if (backupCreated) {
try {
fs.unlinkSync(backup);
} catch (error) {
jobLog(`[replaceOriginalFile] WARNING: Could not remove backup ${backup}: ${error.message}\n`);
}
}
if ((args.inputs.deleteSource || 'true') === 'true') {
try {
fs.unlinkSync(source);
} catch (error) {
jobLog(`[replaceOriginalFile] WARNING: Could not delete processed file ${source}: ${error.message}\n`);
}
}
jobLog(`[replaceOriginalFile] Replaced original file: ${destination}\n`);
return { outputNumber: 1, outputFileObj: { ...inputFileObj, _id: destination }, variables };
} catch (error) {
if (fs.existsSync(temporary)) fs.unlinkSync(temporary);
if (backupCreated && !installed && !fs.existsSync(destination)) fs.renameSync(backup, destination);
throw new Error(`[replaceOriginalFile] Replacement failed: ${error.message}`);
}
};
module.exports.details = details;
module.exports.plugin = plugin;