using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; using FileFlows.Plugin; using FileFlows.Plugin.Attributes; 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 destination path /// [Required] [Folder(1)] public string DestinationPath { get; set; } /// /// Gets or sets the destination file /// [TextVariable(2)] public string DestinationFile{ get; set; } /// /// Gets or sets if the folder should be moved /// [Boolean(3)] public bool MoveFolder { get; set; } /// /// Gets or sets if the original should be deleted /// [Boolean(4)] public bool DeleteOriginal { get; set; } /// /// Gets or sets additional files that should also be moved /// [StringArray(5)] public string[] AdditionalFiles { get; set; } /// /// Gets or sets original files from the original file location that should also be moved /// [Boolean(6)] public bool AdditionalFilesFromOriginal { get; set; } /// /// Gets or sets if the original files creation and last write time dates should be preserved /// [Boolean(7)] public bool PreserverOriginalDates { get; set; } /// /// Executes the node /// /// the node parameters /// the output to call next public override int Execute(NodeParameters args) { var dest = GetDestinationPath(args, DestinationPath, DestinationFile, MoveFolder); if (dest == null) return -1; // store srcDir here before we move and the working file is altered var srcDir = AdditionalFilesFromOriginal ? new FileInfo(args.FileName).DirectoryName : new FileInfo(args.WorkingFile).DirectoryName; if (args.MoveFile(dest) == false) return -1; if(PreserverOriginalDates && 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) { Helpers.FileHelper.SetLastWriteTime(dest, dtLastWriteUtc); Helpers.FileHelper.SetCreationTime(dest, dtCreateTimeUtc); } if(AdditionalFiles?.Any() == true) { try { string destDir = new FileInfo(args.MapPath(dest)).DirectoryName; args.CreateDirectoryIfNotExists(destDir ?? String.Empty); var diSrc = new DirectoryInfo(srcDir); args.Logger?.ILog("Looking for additional files in directory: " + srcDir); foreach (var additional in AdditionalFiles) { args.Logger?.ILog("Looking for additional files: " + additional); foreach(var addFile in diSrc.GetFiles(additional)) { try { args.Logger?.ILog("Additional files: " + addFile.FullName); string addFileDest = Path.Combine(destDir, addFile.Name); System.IO.File.Move(addFile.FullName, addFileDest, true); args.Logger?.ILog("Moved file: \"" + addFile.FullName + "\" to \"" + addFileDest + "\""); } catch(Exception ex) { args.Logger?.ILog("Failed moving file: \"" + addFile.FullName + "\": " + ex.Message); } } } } catch(Exception ex) { args.Logger.WLog("Error moving additional files: " + ex.Message); } } else { args.Logger?.ILog("No additional files configured to move"); } if (DeleteOriginal && args.FileName != args.WorkingFile) { args.Logger?.ILog("Deleting original file: " + args.FileName); try { System.IO.File.Delete(args.FileName); } 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 result.Path + result.Separator + 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, 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.Substring(0, 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); } string destExtension = new FileInfo(destFilename).Extension; string workingExtension = new FileInfo(args.WorkingFile).Extension; if (string.IsNullOrEmpty(destExtension) == false && destExtension != workingExtension) { destFilename = destFilename.Substring(0, destFilename.LastIndexOf(".", StringComparison.Ordinal)) + workingExtension; } args.Logger?.ILog("Final destination path: " + destFolder); args.Logger?.ILog("Final destination filename: " + destFilename); return (destFolder, destFilename, separator); } }