diff --git a/ImageNodes/Images/ImageBaseNode.cs b/ImageNodes/Images/ImageBaseNode.cs
index 0bde6fde..f869e28b 100644
--- a/ImageNodes/Images/ImageBaseNode.cs
+++ b/ImageNodes/Images/ImageBaseNode.cs
@@ -3,14 +3,37 @@ using SixLabors.ImageSharp.Formats;
namespace FileFlows.ImageNodes.Images;
+///
+/// Represents an abstract base class for nodes related to image processing.
+///
public abstract class ImageBaseNode:Node
{
-
+ ///
+ /// Represents the key for storing image information in a context or dictionary.
+ ///
private const string IMAGE_INFO = "ImageInfo";
+
+ ///
+ /// Gets or sets the current format of the image.
+ ///
protected string CurrentFormat { get; private set; }
- protected int CurrentWidth{ get; private set; }
+
+ ///
+ /// Gets or sets the current width of the image.
+ ///
+ protected int CurrentWidth { get; private set; }
+
+ ///
+ /// Gets or sets the current height of the image.
+ ///
protected int CurrentHeight { get; private set; }
+
+ ///
+ /// Calls any pre-execute setup code
+ ///
+ /// The NodeParameters
+ /// true if successful, otherwise false
public override bool PreExecute(NodeParameters args)
{
var localFile = args.FileService.GetLocalPath(args.WorkingFile);
@@ -41,7 +64,12 @@ public abstract class ImageBaseNode:Node
return true;
}
-
+
+ ///
+ /// Updates information about an image based on the provided NodeParameters and optional variables.
+ ///
+ /// The NodeParameters
+ /// Additional variables associated with the image (optional).
protected void UpdateImageInfo(NodeParameters args, Dictionary variables = null)
{
string extension = FileHelper.GetExtension(args.WorkingFile).ToLowerInvariant().TrimStart('.');
@@ -65,7 +93,7 @@ public abstract class ImageBaseNode:Node
else
{
if (string.IsNullOrWhiteSpace(dateTimeOriginalString) == false &&
- DateTime.TryParse(dateTimeOriginalString, out DateTime dateTimeOriginal))
+ TryParseDateTime(dateTimeOriginalString, out DateTime? dateTimeOriginal))
{
dateTaken = dateTimeOriginal;
args.Logger?.ILog("DateTimeOriginal: " + dateTimeOriginal);
@@ -84,6 +112,16 @@ public abstract class ImageBaseNode:Node
UpdateImageInfo(args, image.Width, image.Height, format.Name, variables: variables, dateTaken: dateTaken);
}
}
+
+ ///
+ /// Updates information about an image based on the provided NodeParameters, width, height, format, variables, and dateTaken.
+ ///
+ /// The NodeParameters
+ /// The width of the image.
+ /// The height of the image.
+ /// The format of the image.
+ /// Additional variables associated with the image (optional).
+ /// The date when the image was taken (optional).
protected void UpdateImageInfo(NodeParameters args, int width, int height, string format, Dictionary variables = null, DateTime? dateTaken = null)
{
var imageInfo = new ImageInfo
@@ -104,6 +142,7 @@ public abstract class ImageBaseNode:Node
if (dateTaken != null)
{
+ variables.AddOrUpdate("img.DateTaken.Value", dateTaken.Value);
variables.AddOrUpdate("img.DateTaken.Year", dateTaken.Value.Year);
variables.AddOrUpdate("img.DateTaken.Month", dateTaken.Value.Month);
variables.AddOrUpdate("img.DateTaken.Day", dateTaken.Value.Day);
@@ -118,6 +157,14 @@ public abstract class ImageBaseNode:Node
args.UpdateVariables(variables);
}
+
+ ///
+ /// Gets information about an image based on the provided NodeParameters.
+ ///
+ /// The NodeParameters
+ ///
+ /// An ImageInfo object representing information about the image, or null if information could not be retrieved.
+ ///
internal ImageInfo? GetImageInfo(NodeParameters args)
{
if (args.Parameters.ContainsKey(IMAGE_INFO) == false)
@@ -155,4 +202,42 @@ public abstract class ImageBaseNode:Node
return args.WorkingFile;
}
+
+ ///
+ /// Tries to parse a DateTime from a string, attempting different formats.
+ ///
+ /// The string representation of the DateTime.
+ /// When this method returns, contains the parsed DateTime if successful; otherwise, null.
+ ///
+ /// True if the parsing was successful; otherwise, false.
+ ///
+ static bool TryParseDateTime(string dateTimeString, out DateTime? dateTime)
+ {
+ DateTime parsedDateTime;
+
+ // Try parsing using DateTime.TryParse
+ if (DateTime.TryParse(dateTimeString, out parsedDateTime))
+ {
+ dateTime = parsedDateTime;
+ return true;
+ }
+
+ // Define an array of possible date formats for additional attempts
+ string[] dateFormats = { "yyyy:MM:dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss" /* Add more formats if needed */ };
+
+ // Attempt to parse using different formats
+ foreach (var format in dateFormats)
+ {
+ if (DateTime.TryParseExact(dateTimeString, format, null, System.Globalization.DateTimeStyles.None, out parsedDateTime))
+ {
+ dateTime = parsedDateTime;
+ return true;
+ }
+ }
+
+ // Set dateTime to null if parsing fails with all formats
+ dateTime = null;
+ return false;
+ }
+
}
diff --git a/ImageNodes/Tests/ImageTests.cs b/ImageNodes/Tests/ImageTests.cs
index eb655e30..9003ec62 100644
--- a/ImageNodes/Tests/ImageTests.cs
+++ b/ImageNodes/Tests/ImageTests.cs
@@ -14,7 +14,7 @@ public class ImageNodesTests
string TestImage2;
string TestImageHeic;
string TempDir;
- string TestCropImage1, TestCropImage2, TestCropImage3, TestCropImage4, TestCropImageNoCrop;
+ string TestCropImage1, TestCropImage2, TestCropImage3, TestCropImage4, TestCropImageNoCrop, TestExif;
public ImageNodesTests()
{
@@ -39,6 +39,7 @@ public class ImageNodesTests
TestImage1 = "/home/john/Pictures/circle.jpg";
TestImage2 = "/home/john/Pictures/36410427.png";
TempDir = "/home/john/temp/";
+ TestExif = "/home/john/Pictures/exif_test.jpg";
}
}
@@ -291,6 +292,26 @@ public class ImageNodesTests
string log = logger.ToString();
Assert.AreEqual(2, result);
}
+
+
+ [TestMethod]
+ public void ImageNodes_Basic_Exif()
+ {
+ var logger = new TestLogger();
+ var args = new NodeParameters(TestExif, logger, false, string.Empty, new LocalFileService())
+ {
+ TempPath = TempDir
+ };
+
+ var node = new ImageFile();
+ var result = node.Execute(args);
+ Assert.AreEqual(1, result);
+ if(node.Variables.TryGetValue("img.DateTaken.Value", out object oDate) == false)
+ Assert.Fail("Failed to get date time");
+
+ if(oDate is DateTime dt == false)
+ Assert.Fail("oDate is not a DateTime");
+ }
}
#endif
\ No newline at end of file
diff --git a/ImageNodes/Tests/_LocalFileService.cs b/ImageNodes/Tests/_LocalFileService.cs
new file mode 100644
index 00000000..83b57493
--- /dev/null
+++ b/ImageNodes/Tests/_LocalFileService.cs
@@ -0,0 +1,483 @@
+#if(DEBUG)
+using FileFlows.Plugin;
+using FileFlows.Plugin.Models;
+using FileFlows.Plugin.Services;
+using System.IO;
+using System.Linq;
+
+namespace FileFlows.ImageNodes.Tests;
+
+public class LocalFileService : IFileService
+{
+ ///
+ /// Gets or sets the path separator for the file system
+ ///
+ public char PathSeparator { get; init; } = Path.DirectorySeparatorChar;
+
+ ///
+ /// Gets or sets the allowed paths the file service can access
+ ///
+ public string[] AllowedPaths { get; init; }
+
+ ///
+ /// Gets or sets a function for replacing variables in a string.
+ ///
+ ///
+ /// The function takes a string input, a boolean indicating whether to strip missing variables,
+ /// and a boolean indicating whether to clean special characters.
+ ///
+ public ReplaceVariablesDelegate ReplaceVariables { get; set; }
+
+ ///
+ /// Gets or sets the permissions to use for files
+ ///
+ public int? Permissions { get; set; }
+
+ ///
+ /// Gets or sets the owner:group to use for files
+ ///
+ public string OwnerGroup { get; set; }
+
+ ///
+ /// Gets or sets the logger used for logging
+ ///
+ public ILogger? Logger { get; set; }
+
+ public Result GetFiles(string path, string searchPattern = "", bool recursive = false)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ try
+ {
+ return Directory.GetFiles(path, searchPattern ?? string.Empty,
+ recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
+ }
+ catch (Exception)
+ {
+ return new string[] { };
+ }
+ }
+
+ public Result GetDirectories(string path)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ try
+ {
+ return Directory.GetDirectories(path);
+ }
+ catch (Exception)
+ {
+ return new string[] { };
+ }
+ }
+
+ public Result DirectoryExists(string path)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ try
+ {
+ return Directory.Exists(path);
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ public Result DirectoryDelete(string path, bool recursive = false)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ try
+ {
+ Directory.Delete(path, recursive);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail(ex.Message);
+ }
+ }
+
+ public Result DirectoryMove(string path, string destination)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ if (IsProtectedPath(ref destination))
+ return Result.Fail("Cannot access protected path: " + destination);
+ try
+ {
+ Directory.Move(path, destination);
+ SetPermissions(destination);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail(ex.Message);
+ }
+ }
+
+ public Result DirectoryCreate(string path)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ try
+ {
+ var dirInfo = new DirectoryInfo(path);
+ if (dirInfo.Exists == false)
+ dirInfo.Create();
+ SetPermissions(path);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail(ex.Message);
+ }
+ }
+
+ public Result FileExists(string path)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ try
+ {
+ return File.Exists(path);
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ public Result FileInfo(string path)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ try
+ {
+ FileInfo fileInfo = new FileInfo(path);
+
+ return new FileInformation
+ {
+ CreationTime = fileInfo.CreationTime,
+ CreationTimeUtc = fileInfo.CreationTimeUtc,
+ LastWriteTime = fileInfo.LastWriteTime,
+ LastWriteTimeUtc = fileInfo.LastWriteTimeUtc,
+ Extension = fileInfo.Extension.TrimStart('.'),
+ Name = fileInfo.Name,
+ FullName = fileInfo.FullName,
+ Length = fileInfo.Length,
+ Directory = fileInfo.DirectoryName
+ };
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail(ex.Message);
+ }
+ }
+
+ public Result FileDelete(string path)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ try
+ {
+ var fileInfo = new FileInfo(path);
+ if(fileInfo.Exists)
+ fileInfo.Delete();
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ public Result FileSize(string path)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ try
+ {
+ var fileInfo = new FileInfo(path);
+ if (fileInfo.Exists == false)
+ return Result.Fail("File does not exist");
+ return fileInfo.Length;
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail(ex.Message);
+ }
+ }
+
+ public Result FileCreationTimeUtc(string path)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ try
+ {
+ var fileInfo = new FileInfo(path);
+ if (fileInfo.Exists == false)
+ return Result.Fail("File does not exist");
+ return fileInfo.CreationTimeUtc;
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail(ex.Message);
+ }
+ }
+
+ public Result FileLastWriteTimeUtc(string path)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ try
+ {
+ var fileInfo = new FileInfo(path);
+ if (fileInfo.Exists == false)
+ return Result.Fail("File does not exist");
+ return fileInfo.LastWriteTimeUtc;
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail(ex.Message);
+ }
+ }
+
+ public Result FileMove(string path, string destination, bool overwrite = true)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ if (IsProtectedPath(ref destination))
+ return Result.Fail("Cannot access protected path: " + destination);
+ try
+ {
+ var fileInfo = new FileInfo(path);
+ if (fileInfo.Exists == false)
+ return Result.Fail("File does not exist");
+ var destDir = new FileInfo(destination).Directory;
+ if (destDir.Exists == false)
+ {
+ destDir.Create();
+ SetPermissions(destDir.FullName);
+ }
+
+ fileInfo.MoveTo(destination, overwrite);
+ SetPermissions(destination);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail(ex.Message);
+ }
+ }
+
+ public Result FileCopy(string path, string destination, bool overwrite = true)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ if (IsProtectedPath(ref destination))
+ return Result.Fail("Cannot access protected path: " + destination);
+ try
+ {
+ var fileInfo = new FileInfo(path);
+ if (fileInfo.Exists == false)
+ return Result.Fail("File does not exist");
+
+ var destDir = new FileInfo(destination).Directory;
+ if (destDir.Exists == false)
+ {
+ destDir.Create();
+ SetPermissions(destDir.FullName);
+ }
+
+ fileInfo.CopyTo(destination, overwrite);
+ SetPermissions(destination);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail(ex.Message);
+ }
+ }
+
+ public Result FileAppendAllText(string path, string text)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ try
+ {
+ File.AppendAllText(path, text);
+ SetPermissions(path);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail(ex.Message);
+ }
+ }
+
+ public bool FileIsLocal(string path) => true;
+
+ ///
+ /// Gets the local path
+ ///
+ /// the path
+ /// the local path to the file
+ public Result GetLocalPath(string path)
+ => Result.Success(path);
+
+ public Result Touch(string path)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+
+ if (DirectoryExists(path).Is(true))
+ {
+ try
+ {
+ Directory.SetLastWriteTimeUtc(path, DateTime.UtcNow);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail("Failed to touch directory: " + ex.Message);
+ }
+ }
+
+ try
+ {
+ if (File.Exists(path))
+ File.SetLastWriteTimeUtc(path, DateTime.UtcNow);
+ else
+ {
+ File.Create(path);
+ SetPermissions(path);
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail($"Failed to touch file: '{path}' => {ex.Message}");
+ }
+ }
+
+ public Result SetCreationTimeUtc(string path, DateTime date)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ try
+ {
+ if (!File.Exists(path))
+ return Result.Fail("File not found.");
+
+ File.SetCreationTimeUtc(path, date);
+ return Result.Success(true);
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail($"Error setting creation time: {ex.Message}");
+ }
+ }
+
+ public Result SetLastWriteTimeUtc(string path, DateTime date)
+ {
+ if (IsProtectedPath(ref path))
+ return Result.Fail("Cannot access protected path: " + path);
+ try
+ {
+ if (!File.Exists(path))
+ return Result.Fail("File not found.");
+
+ File.SetLastWriteTimeUtc(path, date);
+ return Result.Success(true);
+ }
+ catch (Exception ex)
+ {
+ return Result.Fail($"Error setting last write time: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Checks if a path is accessible by the file server
+ ///
+ /// the path to check
+ /// true if accessible, otherwise false
+ private bool IsProtectedPath(ref string path)
+ {
+ if (OperatingSystem.IsWindows())
+ path = path.Replace("/", "\\");
+ else
+ path = path.Replace("\\", "/");
+
+ if(ReplaceVariables != null)
+ path = ReplaceVariables(path, true);
+
+ if (FileHelper.IsSystemDirectory(path))
+ return true; // a system directory, no access
+
+ if (AllowedPaths?.Any() != true)
+ return false; // no allowed paths configured, allow all
+
+ if (OperatingSystem.IsWindows())
+ path = path.ToLowerInvariant();
+
+ for(int i=0;i logMethod = null)
+ {
+ logMethod ??= (string message) => Logger?.ILog(message);
+
+ permissions = permissions != null && permissions > 0 ? permissions : Permissions;
+ if (permissions == null || permissions < 1)
+ permissions = 777;
+
+
+ if ((File.Exists(path) == false && Directory.Exists(path) == false))
+ {
+ logMethod("SetPermissions: File doesnt existing, skipping");
+ return;
+ }
+
+ //StringLogger stringLogger = new StringLogger();
+ var logger = new TestLogger();
+
+ bool isFile = new FileInfo(path).Exists;
+
+ FileHelper.SetPermissions(logger, path, file: isFile,
+ permissions: permissions.Value.ToString("D3"));
+
+ FileHelper.ChangeOwner(logger, path, file: isFile, ownerGroup: OwnerGroup);
+
+ logMethod(logger.ToString());
+
+ return;
+
+
+ if (OperatingSystem.IsLinux())
+ {
+ var filePermissions = FileHelper.ConvertLinuxPermissionsToUnixFileMode(permissions.Value);
+ if (filePermissions == UnixFileMode.None)
+ {
+ logMethod("SetPermissions: Invalid file permissions: " + permissions.Value);
+ return;
+ }
+
+ File.SetUnixFileMode(path, filePermissions);
+ logMethod($"SetPermissions: Permission [{filePermissions}] set on file: " + path);
+ }
+
+ }
+}
+#endif
\ No newline at end of file