feat(flow): support folder-to-folder conversion with sidecar migration and prune
Build / Lint & Test (push) Successful in 28s

This commit is contained in:
PickleRick committed 2026-09-30 18:43:12 +02:00
1 parent f2e483ca1f
commit 88ecf9a847
3 files changed
+253 -62

No files matched your search

@@ -27,6 +27,14 @@ const details = () => ({
inputUI: { type: 'dropdown', options: ['true', 'false'] },
tooltip: 'Delete the processed file after it has replaced the original.',
},
{
label: 'Output Directory (Optional)',
name: 'outputDirectory',
type: 'string',
defaultValue: '',
inputUI: { type: 'text' },
tooltip: 'Target library directory for folder-to-folder conversion. If empty, automatically uses Library Output if configured, or replaces in-place. Example: T:/Media/Film or T:/Media/Serie',
},
],
outputs: [
{ number: 1, tooltip: 'Original file replaced — continue to next plugin' },
@@ -47,6 +55,36 @@ const plugin = async (args) => {
if (!fs.existsSync(source)) {
throw new Error(`[replaceOriginalFile] Processed file not found: ${source}`);
}
const originalSourceFile = destination;
const originalSourceDir = path.dirname(originalSourceFile);
// Check for folder-to-folder target directory (from plugin input or Tdarr librarySettings.output)
const configuredOutput = ((args.inputs && args.inputs.outputDirectory) || '').trim()
|| ((args.librarySettings && args.librarySettings.output) || '').trim();
const sourceLibraryRoot = ((args.librarySettings && args.librarySettings.folder) || '').trim();
if (configuredOutput && path.resolve(configuredOutput) !== path.resolve(sourceLibraryRoot || originalSourceDir)) {
let relPath = '';
if (sourceLibraryRoot) {
try {
relPath = path.relative(path.resolve(sourceLibraryRoot), path.resolve(originalSourceFile));
} catch (_) { relPath = ''; }
}
if (!relPath || relPath.startsWith('..') || path.isAbsolute(relPath)) {
const base = path.basename(originalSourceFile);
const parent = path.basename(originalSourceDir);
const grandParent = path.basename(path.dirname(originalSourceDir));
if (grandParent && grandParent !== '.' && !grandParent.includes(':')) {
relPath = path.join(grandParent, parent, base);
} else {
relPath = path.join(parent, base);
}
}
destination = path.join(path.resolve(configuredOutput), relPath);
jobLog(`[replaceOriginalFile] Folder-to-folder mode: target is ${destination}\n`);
}
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 };
@@ -54,7 +92,8 @@ const plugin = async (args) => {
const destinationDir = path.dirname(destination);
if (!fs.existsSync(destinationDir)) {
throw new Error(`[replaceOriginalFile] Destination directory does not exist: ${destinationDir}`);
fs.mkdirSync(destinationDir, { recursive: true });
jobLog(`[replaceOriginalFile] Created destination directory: ${destinationDir}\n`);
}
const sourceExt = path.extname(source);
@@ -107,7 +146,7 @@ const plugin = async (args) => {
}
}
if ((args.inputs.deleteSource || 'true') === 'true') {
if ((args.inputs.deleteSource || 'true') === 'true' && path.resolve(source) !== path.resolve(destination)) {
try {
fs.unlinkSync(source);
} catch (error) {
@@ -115,6 +154,15 @@ const plugin = async (args) => {
}
}
if (path.resolve(destination) !== path.resolve(originalSourceFile) && fs.existsSync(originalSourceFile)) {
try {
fs.unlinkSync(originalSourceFile);
jobLog(`[replaceOriginalFile] Removed source file from intake directory: ${originalSourceFile}\n`);
} catch (error) {
jobLog(`[replaceOriginalFile] WARNING: Could not delete intake source file ${originalSourceFile}: ${error.message}\n`);
}
}
// Clean up temporary workDir copy (_old) if still present
if (variables.workDirCopyApplied === 'true') {
const workDir = path.dirname(source);
@@ -125,6 +173,9 @@ const plugin = async (args) => {
}
}
variables.finalFile = destination;
variables.finalDir = destinationDir;
jobLog(`[replaceOriginalFile] Replaced original file: ${destination}\n`);
return {
outputNumber: 1,