FF-346 - preserving last write time and attempting to preserve creation time of files if set (filesystem has to support these)

This commit is contained in:
John Andrews
2023-06-13 17:45:46 +12:00
parent ae59da110f
commit 53c637b037
9 changed files with 328 additions and 144 deletions

View File

@@ -43,6 +43,13 @@ namespace FileFlows.AudioNodes
if (string.IsNullOrEmpty(ffmpegExe))
return -1;
var fileInfo = new FileInfo(args.WorkingFile);
if (fileInfo.Exists)
{
args.Variables.Add("ORIGINAL_CREATE_UTC", fileInfo.CreationTimeUtc);
args.Variables.Add("ORIGINAL_LAST_WRITE_UTC", fileInfo.LastWriteTimeUtc);
}
try
{
if (ReadAudioFileInfo(args, ffmpegExe, args.WorkingFile))

View File

@@ -36,7 +36,9 @@
"AdditionalFiles": "Additional Files",
"AdditionalFiles-Help": "Additional files to copy from the directory to the new directory.\nEach value can contain a combination of valid literal path and wildcard (* and ?) characters, but it doesn''t support regular expressions.",
"AdditionalFilesFromOriginal": "Original Directory",
"AdditionalFilesFromOriginal-Help": "If the additional files should be copied from the working directory or from the original directory. Turn on for original directory."
"AdditionalFilesFromOriginal-Help": "If the additional files should be copied from the working directory or from the original directory. Turn on for original directory.",
"PreserverOriginalDates": "Preserve Dates",
"PreserverOriginalDates-Help": "If the original last write time of the original input file should be preserved."
}
},
"DeleteSourceDirectory": {
@@ -192,7 +194,9 @@
"AdditionalFiles": "Additional Files",
"AdditionalFiles-Help": "Additional files to move from the directory to the new directory.\nEach value can contain a combination of valid literal path and wildcard (* and ?) characters, but it doesn''t support regular expressions.",
"AdditionalFilesFromOriginal": "Original Directory",
"AdditionalFilesFromOriginal-Help": "If the additional files should be moved from the working directory or from the original directory. Turn on for original directory."
"AdditionalFilesFromOriginal-Help": "If the additional files should be moved from the working directory or from the original directory. Turn on for original directory.",
"PreserverOriginalDates": "Preserve Dates",
"PreserverOriginalDates-Help": "If the original last write time of the original input file should be preserved."
}
},
"OlderThan": {
@@ -243,6 +247,10 @@
"Description": "Replaces the original file with the working file.\n\nIf the extension is different on the working file, the original file will be deleted and the working file will be moved to the original with the new extension.\nE.g. from File.avi to File.mkv",
"Outputs": {
"1": "Original file replaced"
},
"Fields": {
"PreserverOriginalDates": "Preserve Dates",
"PreserverOriginalDates-Help": "If the original last write time of the original input file should be preserved."
}
},
"Renamer": {

View File

@@ -61,6 +61,12 @@ namespace FileFlows.BasicNodes.File
[Boolean(5)]
public bool AdditionalFilesFromOriginal { get; set; }
/// <summary>
/// Gets or sets if the original files creation and last write time dates should be preserved
/// </summary>
[Boolean(6)]
public bool PreserverOriginalDates { get; set; }
private bool Canceled;
public override Task Cancel()
@@ -123,6 +129,15 @@ namespace FileFlows.BasicNodes.File
if (!copied)
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.SetCreationTime(dest, dtCreateTimeUtc);
Helpers.FileHelper.SetLastWriteTime(dest, dtLastWriteUtc);
}
var srcDir = AdditionalFilesFromOriginal ? new FileInfo(args.MapPath(args.FileName)).DirectoryName : new FileInfo(args.MapPath(args.WorkingFile)).DirectoryName;
if (AdditionalFiles?.Any() == true)

View File

@@ -1,34 +1,52 @@
namespace FileFlows.BasicNodes.File
using FileFlows.Plugin;
namespace FileFlows.BasicNodes.File;
/// <summary>
/// Basic Input file, the default input node
/// </summary>
public class InputFile : Node
{
using System.ComponentModel;
using FileFlows.Plugin;
using FileFlows.Plugin.Attributes;
/// <summary>
/// Gets the number of outputs
/// </summary>
public override int Outputs => 1;
/// <summary>
/// Gets the element type
/// </summary>
public override FlowElementType Type => FlowElementType.Input;
/// <summary>
/// Gets the icon
/// </summary>
public override string Icon => "far fa-file";
/// <summary>
/// Gets the help URL
/// </summary>
public override string HelpUrl => "https://docs.fileflows.com/plugins/basic-nodes/input-file";
public class InputFile : Node
/// <summary>
/// Executes the node
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>the output to call next</returns>
public override int Execute(NodeParameters args)
{
public override int Outputs => 1;
public override FlowElementType Type => FlowElementType.Input;
public override string Icon => "far fa-file";
public override string HelpUrl => "https://docs.fileflows.com/plugins/basic-nodes/input-file";
public override int Execute(NodeParameters args)
try
{
try
FileInfo fileInfo = new FileInfo(args.WorkingFile);
if (fileInfo.Exists == false)
{
FileInfo fileInfo = new FileInfo(args.WorkingFile);
if (fileInfo.Exists == false)
{
args.Logger?.ELog("File not found: " + args.WorkingFile);
return -1;
}
return 1;
}
catch (Exception ex)
{
args.Logger?.ELog("Failed in InputFile: " + ex.Message + Environment.NewLine + ex.StackTrace);
args.Logger?.ELog("File not found: " + args.WorkingFile);
return -1;
}
args.Variables.Add("ORIGINAL_CREATE_UTC", fileInfo.CreationTimeUtc);
args.Variables.Add("ORIGINAL_LAST_WRITE_UTC", fileInfo.LastWriteTimeUtc);
return 1;
}
catch (Exception ex)
{
args.Logger?.ELog("Failed in InputFile: " + ex.Message + Environment.NewLine + ex.StackTrace);
return -1;
}
}
}

View File

@@ -1,143 +1,194 @@
namespace FileFlows.BasicNodes.File
using System.ComponentModel.DataAnnotations;
using FileFlows.Plugin;
using FileFlows.Plugin.Attributes;
namespace FileFlows.BasicNodes.File;
/// <summary>
/// Moves a file to a new location
/// </summary>
public class MoveFile : Node
{
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using FileFlows.Plugin;
using FileFlows.Plugin.Attributes;
/// <summary>
/// Gets the number of inputs
/// </summary>
public override int Inputs => 1;
/// <summary>
/// Gets the number of outputs
/// </summary>
public override int Outputs => 2;
/// <summary>
/// Gets the type of flow element
/// </summary>
public override FlowElementType Type => FlowElementType.Process;
/// <summary>
/// Gets the icon for the flow element
/// </summary>
public override string Icon => "fas fa-file-export";
/// <summary>
/// Gets the help URL
/// </summary>
public override string HelpUrl => "https://docs.fileflows.com/plugins/basic-nodes/move-file";
public class MoveFile : Node
/// <summary>
/// Gets or sets the destination path
/// </summary>
[Required]
[Folder(1)]
public string DestinationPath { get; set; }
/// <summary>
/// Gets or sets the destination file
/// </summary>
[TextVariable(2)]
public string DestinationFile{ get; set; }
/// <summary>
/// Gets or sets if the folder should be moved
/// </summary>
[Boolean(3)]
public bool MoveFolder { get; set; }
/// <summary>
/// Gets or sets if the original should be deleted
/// </summary>
[Boolean(4)]
public bool DeleteOriginal { get; set; }
/// <summary>
/// Gets or sets additional files that should also be moved
/// </summary>
[StringArray(5)]
public string[] AdditionalFiles { get; set; }
/// <summary>
/// Gets or sets original files from the original file location that should also be moved
/// </summary>
[Boolean(6)]
public bool AdditionalFilesFromOriginal { get; set; }
/// <summary>
/// Gets or sets if the original files creation and last write time dates should be preserved
/// </summary>
[Boolean(7)]
public bool PreserverOriginalDates { get; set; }
/// <summary>
/// Executes the node
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>the output to call next</returns>
public override int Execute(NodeParameters args)
{
public override int Inputs => 1;
public override int Outputs => 2;
public override FlowElementType Type => FlowElementType.Process;
public override string Icon => "fas fa-file-export";
var dest = GetDesitnationPath(args, DestinationPath, DestinationFile, MoveFolder);
if (dest == null)
return -1;
public override string HelpUrl => "https://docs.fileflows.com/plugins/basic-nodes/move-file";
string destDir = new FileInfo(dest).DirectoryName;
[Required]
[Folder(1)]
public string DestinationPath { get; set; }
args.CreateDirectoryIfNotExists(destDir ?? String.Empty);
[TextVariable(2)]
public string DestinationFile{ get; set; }
var srcDir = AdditionalFilesFromOriginal ? new FileInfo(args.FileName).DirectoryName : new FileInfo(args.WorkingFile).DirectoryName;
[Boolean(3)]
public bool MoveFolder { get; set; }
[Boolean(4)]
public bool DeleteOriginal { get; set; }
[StringArray(5)]
public string[] AdditionalFiles { get; set; }
[Boolean(6)]
public bool AdditionalFilesFromOriginal { get; set; }
public override int Execute(NodeParameters args)
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)
{
var dest = GetDesitnationPath(args, DestinationPath, DestinationFile, MoveFolder);
if (dest == null)
return -1;
Helpers.FileHelper.SetLastWriteTime(dest, dtLastWriteUtc);
Helpers.FileHelper.SetCreationTime(dest, dtCreateTimeUtc);
}
string destDir = new FileInfo(dest).DirectoryName;
args.CreateDirectoryIfNotExists(destDir ?? String.Empty);
var srcDir = AdditionalFilesFromOriginal ? new FileInfo(args.FileName).DirectoryName : new FileInfo(args.WorkingFile).DirectoryName;
if (args.MoveFile(dest) == false)
return -1;
if(AdditionalFiles?.Any() == true)
if(AdditionalFiles?.Any() == true)
{
try
{
try
var diSrc = new DirectoryInfo(srcDir);
foreach (var additional in AdditionalFiles)
{
var diSrc = new DirectoryInfo(srcDir);
foreach (var additional in AdditionalFiles)
foreach(var addFile in diSrc.GetFiles(additional))
{
foreach(var addFile in diSrc.GetFiles(additional))
try
{
try
{
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);
}
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);
}
}
if (DeleteOriginal && args.FileName != args.WorkingFile)
catch(Exception ex)
{
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;
}
args.Logger.WLog("Error moving additional files: " + ex.Message);
}
return 1;
}
internal static string GetDesitnationPath(NodeParameters args, string destinationPath, string destinationFile = null, bool moveFolder = false)
if (DeleteOriginal && args.FileName != args.WorkingFile)
{
string dest = args.ReplaceVariables(destinationPath, true);
dest = dest.Replace("\\", Path.DirectorySeparatorChar.ToString());
dest = dest.Replace("/", Path.DirectorySeparatorChar.ToString());
if (string.IsNullOrEmpty(dest))
args.Logger?.ILog("Deleting original file: " + args.FileName);
try
{
args.Logger?.ELog("No destination specified");
args.Result = NodeResult.Failure;
return null;
System.IO.File.Delete(args.FileName);
}
args.Result = NodeResult.Failure;
if (moveFolder)
dest = Path.Combine(dest, args.RelativeFile);
else
dest = Path.Combine(dest, new FileInfo(args.FileName).Name);
var fiDest = new FileInfo(dest);
var fiWorking = new FileInfo(args.WorkingFile);
if (string.IsNullOrEmpty(fiDest.Extension) == false && fiDest.Extension != fiWorking.Extension)
catch(Exception ex)
{
dest = dest.Substring(0, dest.LastIndexOf(".")) + fiWorking.Extension;
args.Logger?.WLog("Failed to delete original file: " + ex.Message);
return 2;
}
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}");
string destFile = args.ReplaceVariables(destinationFile);
dest = Path.Combine(new FileInfo(dest).DirectoryName!, destFile);
}
fiDest = new FileInfo(dest);
var fiWorkingFile = new FileInfo(args.WorkingFile);
if (fiDest.Extension != fiWorkingFile.Extension)
{
dest = dest.Replace(fiDest.Extension, fiWorkingFile.Extension);
fiDest = new FileInfo(dest);
}
return dest;
}
return 1;
}
internal static string GetDesitnationPath(NodeParameters args, string destinationPath, string destinationFile = null, bool moveFolder = false)
{
string dest = args.ReplaceVariables(destinationPath, true);
dest = dest.Replace("\\", Path.DirectorySeparatorChar.ToString());
dest = dest.Replace("/", Path.DirectorySeparatorChar.ToString());
if (string.IsNullOrEmpty(dest))
{
args.Logger?.ELog("No destination specified");
args.Result = NodeResult.Failure;
return null;
}
args.Result = NodeResult.Failure;
if (moveFolder)
dest = Path.Combine(dest, args.RelativeFile);
else
dest = Path.Combine(dest, new FileInfo(args.FileName).Name);
var fiDest = new FileInfo(dest);
var fiWorking = new FileInfo(args.WorkingFile);
if (string.IsNullOrEmpty(fiDest.Extension) == false && fiDest.Extension != fiWorking.Extension)
{
dest = dest.Substring(0, dest.LastIndexOf(".")) + fiWorking.Extension;
}
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}");
string destFile = args.ReplaceVariables(destinationFile);
dest = Path.Combine(new FileInfo(dest).DirectoryName!, destFile);
}
fiDest = new FileInfo(dest);
var fiWorkingFile = new FileInfo(args.WorkingFile);
if (fiDest.Extension != fiWorkingFile.Extension)
{
dest = dest.Replace(fiDest.Extension, fiWorkingFile.Extension);
fiDest = new FileInfo(dest);
}
return dest;
}
}

View File

@@ -1,19 +1,47 @@
using FileFlows.Plugin;
using FileFlows.Plugin.Attributes;
namespace FileFlows.BasicNodes.File;
/// <summary>
/// Replaces the original file
/// </summary>
public class ReplaceOriginal : Node
{
/// <summary>
/// Gets the number of inputs
/// </summary>
public override int Inputs => 1;
/// <summary>
/// Gets the number of outputs
/// </summary>
public override int Outputs => 1;
/// <summary>
/// Gets the icon to use
/// </summary>
public override string Icon => "fas fa-file";
/// <summary>
/// Gets the help URL
/// </summary>
public override string HelpUrl => "https://docs.fileflows.com/plugins/basic-nodes/replace-original";
public string _Pattern = string.Empty;
/// <summary>
/// Gets the type of flow element
/// </summary>
public override FlowElementType Type => FlowElementType.Process;
public string _Pattern = string.Empty;
/// <summary>
/// Gets or sets if the original files creation and last write time dates should be preserved
/// </summary>
[Boolean(1)]
public bool PreserverOriginalDates { get; set; }
/// <summary>
/// Executes the node
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>the next output to execute</returns>
public override int Execute(NodeParameters args)
{
if (args.FileName == args.WorkingFile)
@@ -31,6 +59,14 @@ public class ReplaceOriginal : Node
args.Logger?.ELog("Failed to move file to: "+ args.FileName);
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.SetCreationTime(args.FileName, dtCreateTimeUtc);
Helpers.FileHelper.SetLastWriteTime(args.FileName, dtLastWriteUtc);
}
}
else
{
@@ -53,6 +89,15 @@ public class ReplaceOriginal : Node
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.SetCreationTime(dest, dtCreateTimeUtc);
Helpers.FileHelper.SetLastWriteTime(dest, dtLastWriteUtc);
}
}
return 1;

View File

@@ -0,0 +1,26 @@
using System.Diagnostics;
namespace FileFlows.BasicNodes.Helpers;
/// <summary>
/// File Helper
/// </summary>
public static class FileHelper
{
/// <summary>
/// Sets the last write time of a file
/// </summary>
/// <param name="filePath">the file path</param>
/// <param name="utcDate">the UTC to set</param>
public static void SetLastWriteTime(string filePath, DateTime utcDate)
=> System.IO.File.SetLastWriteTimeUtc(filePath, utcDate);
/// <summary>
/// Sets the last write time of a file
/// </summary>
/// <param name="filePath">the file path</param>
/// <param name="utcDate">the UTC to set</param>
public static void SetCreationTime(string filePath, DateTime utcDate)
=> System.IO.File.SetCreationTimeUtc(filePath, utcDate);
}

View File

@@ -28,6 +28,13 @@ public class ImageFile : ImageBaseNode
{
try
{
var fileInfo = new FileInfo(args.WorkingFile);
if (fileInfo.Exists)
{
args.Variables.Add("ORIGINAL_CREATE_UTC", fileInfo.CreationTimeUtc);
args.Variables.Add("ORIGINAL_LAST_WRITE_UTC", fileInfo.LastWriteTimeUtc);
}
UpdateImageInfo(args, this.Variables);
if(string.IsNullOrEmpty(base.CurrentFormat) == false)
args.RecordStatistic("IMAGE_FORMAT", base.CurrentFormat);

View File

@@ -67,6 +67,13 @@ public class VideoFile : VideoNode
args.Logger.ILog($"Video stream '{vs.Codec}' '{vs.Index}'");
}
var fileInfo = new FileInfo(args.WorkingFile);
if (fileInfo.Exists)
{
args.Variables.Add("ORIGINAL_CREATE_UTC", fileInfo.CreationTimeUtc);
args.Variables.Add("ORIGINAL_LAST_WRITE_UTC", fileInfo.LastWriteTimeUtc);
}
foreach (var stream in videoInfo.VideoStreams)
{
if (string.IsNullOrEmpty(stream.Codec) == false)