using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; using FileFlows.BasicNodes.Helpers; using FileFlows.Plugin; using FileFlows.Plugin.Attributes; using FileFlows.Plugin.Helpers; namespace FileFlows.BasicNodes.File; /// /// Moves a file to a new location /// public class MoveFile : Node { /// /// Gets the number of inputs /// public override int Inputs => 1; /// /// Gets the number of outputs /// public override int Outputs => 2; /// /// Gets the type of flow element /// public override FlowElementType Type => FlowElementType.Process; /// /// Gets the icon for the flow element /// public override string Icon => "fas fa-file-export"; /// /// Gets the help URL /// public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/move-file"; /// /// Gets or sets the input file to move /// [TextVariable(1)] public string InputFile{ get; set; } /// /// Gets or sets the destination path /// [Required] [Folder(2)] public string DestinationPath { get; set; } /// /// Gets or sets the destination file /// [TextVariable(3)] public string DestinationFile{ get; set; } /// /// Gets or sets if the folder should be moved /// [Boolean(4)] public bool MoveFolder { get; set; } /// /// Gets or sets if the original should be deleted /// [Boolean(5)] public bool DeleteOriginal { get; set; } /// /// Gets or sets additional files that should also be moved /// [StringArray(6)] public string[] AdditionalFiles { get; set; } /// /// Gets or sets original files from the original file location that should also be moved /// [Boolean(7)] public bool AdditionalFilesFromOriginal { get; set; } /// /// Gets or sets if the original files creation and last write time dates should be preserved /// [Boolean(8)] public bool PreserverOriginalDates { get; set; } /// /// Executes the node /// /// the node parameters /// the output to call next public override int Execute(NodeParameters args) { string destFile = args.ReplaceVariables(DestinationFile ?? string.Empty, stripMissing: true); string inputFile = args.ReplaceVariables(InputFile ?? string.Empty, stripMissing: true)?.EmptyAsNull() ?? args.WorkingFile; if (inputFile != args.WorkingFile && string.IsNullOrWhiteSpace(DestinationFile)) destFile = FileHelper.GetShortFileName(inputFile); var dest = GetDestinationPath(args, DestinationPath, destFile, MoveFolder); if (dest == null) { args.FailureReason = "Failed to get move destination"; args.Logger?.ELog(args.FailureReason); return -1; } if (inputFile != args.WorkingFile && string.IsNullOrWhiteSpace(destFile) == false) dest = FileHelper.Combine(FileHelper.GetDirectory(dest), destFile); // ensure the non working file has correct extension/name // store srcDir here before we move and the working file is altered var srcDir = FileHelper.GetDirectory(AdditionalFilesFromOriginal ? args.FileName : inputFile); args.Logger?.ILog("Source Directory: " + srcDir); string shortNameLookup = FileHelper.GetShortFileName(args.FileName); if (shortNameLookup.LastIndexOf(".", StringComparison.InvariantCulture) > 0) shortNameLookup = shortNameLookup[..shortNameLookup.LastIndexOf(".", StringComparison.Ordinal)]; args.Logger?.ILog("shortNameLookup: " + shortNameLookup); args.Logger?.ILog("Moving file: " + inputFile); args.Logger?.ILog("Destination: " + dest); if (inputFile == args.WorkingFile) { if (args.MoveFile(dest) == false) { args.FailureReason = "Failed to move file"; args.Logger?.ELog(args.FailureReason); return -1; } } else { if (args.FileService.FileMove(inputFile, dest).Failed(out string error)) { args.FailureReason = "Failed to move file: " + error; args.Logger?.ELog(args.FailureReason); return -1; } } if (PreserverOriginalDates) { if (args.Variables.TryGetValue("ORIGINAL_CREATE_UTC", out object oCreateTimeUtc) && args.Variables.TryGetValue("ORIGINAL_LAST_WRITE_UTC", out object oLastWriteUtc) && oCreateTimeUtc is DateTime dtCreateTimeUtc && oLastWriteUtc is DateTime dtLastWriteUtc) { args.Logger?.ILog("Preserving dates"); args.FileService.SetLastWriteTimeUtc(dest, dtLastWriteUtc); args.FileService.SetCreationTimeUtc(dest, dtCreateTimeUtc); } else { args.Logger?.WLog("Preserve dates is on but failed to get original dates from variables"); } } if(AdditionalFiles?.Any() == true) { args.Logger?.ILog("Additional Files: " + string.Join(", ", AdditionalFiles)); var addFiles = FolderHelper.GetAdditionalFiles(args.Logger, args.FileService, args.ReplaceVariables, shortNameLookup, srcDir, AdditionalFiles); string destDir = FileHelper.GetDirectory(dest); foreach (var addFile in addFiles) { try { args.Logger?.ILog("Additional files: " + addFile); string addFileDest = FileHelper.Combine(destDir, FileHelper.GetShortFileName(addFile)); args.FileService.FileMove(addFile, addFileDest, true); args.Logger?.ILog("Moved file: \"" + addFile + "\" to \"" + addFileDest + "\""); } catch(Exception ex) { args.Logger?.ILog("Failed moving file: \"" + addFile + "\": " + ex.Message); } } } else { args.Logger?.ILog("No additional files configured to move"); } if (DeleteOriginal && args.LibraryFileName != args.WorkingFile) { args.Logger?.ILog("Deleting original file: " + args.LibraryFileName); try { args.FileService.FileDelete(args.LibraryFileName); } catch(Exception ex) { args.Logger?.WLog("Failed to delete original file: " + ex.Message); return 2; } } return 1; } /// /// Gets the full destination path /// /// the node parameters /// the requested destination path /// the requested destination file /// if the relative folder should be also be included, relative to the library /// the full destination path internal static string GetDestinationPath(NodeParameters args, string destinationPath, string destinationFile = null, bool moveFolder = false) { var result = GetDestinationPathParts(args, destinationPath, destinationFile, moveFolder); if(result.Filename == null) return null; return FileHelper.Combine(result.Path, result.Filename); } /// /// Gets the destination path and filename /// /// the node parameters /// the requested destination path /// the requested destination file /// if the relative folder should be also be included, relative to the library /// the path and filename internal static (string? Path, string? Filename, string? Separator) GetDestinationPathParts(NodeParameters args, string destinationPath, string destinationFile = null, bool moveFolder = false) { string separator = args.WorkingFile.IndexOf('/') >= 0 ? "/" : "\\"; string destFolder = args.ReplaceVariables(destinationPath, stripMissing: true); destFolder = destFolder.Replace("\\", separator); destFolder = destFolder.Replace("/", separator); string destFilename = args.FileName.Replace("\\", separator) .Replace("/", separator); destFilename = destFilename.Substring(destFilename.LastIndexOf(separator, StringComparison.Ordinal) + 1); if (string.IsNullOrEmpty(destFolder)) { args.Logger?.ELog("No destination specified"); args.Result = NodeResult.Failure; return (null, null, null); } args.Result = NodeResult.Failure; if (moveFolder) // we only want the full directory relative to the library, we don't want the original filename { args.Logger?.ILog("Relative File: " + args.RelativeFile); string relative = args.RelativeFile.Replace("\\", separator).Replace("/", separator); if (relative.StartsWith(separator)) relative = relative[1..]; if (relative.IndexOf(separator, StringComparison.Ordinal) > 0) { destFolder = destFolder + separator + relative[..relative.LastIndexOf(separator, StringComparison.Ordinal)]; args.Logger?.ILog("Using relative directory: " + destFolder); } } // dest = Path.Combine(dest, argsFilename); if (string.IsNullOrEmpty(destinationFile) == false) { // FF-154 - changed file.Name and file.Orig.Filename to be the full short filename including the extension destinationFile = destinationFile.Replace("{file.Orig.FileName}{file.Orig.Extension}", "{file.Orig.FileName}"); destinationFile = destinationFile.Replace("{file.Name}{file.Extension}", "{file.Name}"); destinationFile = destinationFile.Replace("{file.Name}{ext}", "{file.Name}"); destFilename = args.ReplaceVariables(destinationFile, stripMissing: true); } string destExtension = FileHelper.GetExtension(destFilename).TrimStart('.'); string workingExtension = FileHelper.GetExtension(args.WorkingFile).TrimStart('.'); if (string.IsNullOrEmpty(destExtension) == false && destExtension != workingExtension) { destFilename = destFilename[..(destFilename.LastIndexOf(".", StringComparison.Ordinal) + 1)] + workingExtension; } args.Logger?.ILog("Final destination path: " + destFolder); args.Logger?.ILog("Final destination filename: " + destFilename); return (destFolder, destFilename, separator); } }