'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;