diff --git a/BasicNodes/Functions/Function.cs b/BasicNodes/Functions/Function.cs
index 89476355..d961928c 100644
--- a/BasicNodes/Functions/Function.cs
+++ b/BasicNodes/Functions/Function.cs
@@ -3,28 +3,40 @@ using FileFlows.Plugin;
using FileFlows.Plugin.Attributes;
using System.ComponentModel.DataAnnotations;
-
namespace FileFlows.BasicNodes.Functions;
+///
+/// A flow element that executes custom code
+///
public class Function : Node
{
+ ///
public override int Inputs => 1;
+ ///
public override FlowElementType Type => FlowElementType.Logic;
+ ///
public override string Icon => "fas fa-code";
+ ///
public override bool FailureNode => true;
-
+ ///
public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/function";
+ ///
+ /// Gets or sets the number of outputs
+ ///
[DefaultValue(1)]
[NumberInt(1)]
public new int Outputs { get; set; }
+ ///
+ /// Gets or sets the code to execute
+ ///
[Required]
[DefaultValue("// Custom javascript code that you can run against the flow file.\n// Flow contains helper functions for the Flow.\n// Variables contain variables available to this node from previous nodes.\n// Logger lets you log messages to the flow output.\n\n// return 0 to complete the flow.\n// return -1 to signal an error in the flow\n// return 1+ to select which output node will be processed next\n\nif(Variables.file.Size === 0)\n\treturn -1;\n\nreturn 1;")]
[Code(2)]
public string Code { get; set; }
-
- delegate void LogDelegate(params object[] values);
+
+ ///
public override int Execute(NodeParameters args)
{
if (string.IsNullOrEmpty(Code))
diff --git a/BasicNodes/Functions/Matches.cs b/BasicNodes/Functions/Matches.cs
new file mode 100644
index 00000000..3f38c239
--- /dev/null
+++ b/BasicNodes/Functions/Matches.cs
@@ -0,0 +1,81 @@
+using FileFlows.Plugin;
+using FileFlows.Plugin.Attributes;
+using System.ComponentModel.DataAnnotations;
+using System.Text.RegularExpressions;
+using FileFlows.BasicNodes.Helpers;
+
+namespace FileFlows.BasicNodes.Functions;
+
+///
+/// A flow element that matches different conditions
+///
+public class Matches : Node
+{
+ ///
+ public override int Inputs => 1;
+ ///
+ public override FlowElementType Type => FlowElementType.Logic;
+ ///
+ public override string Icon => "fas fa-equals";
+ ///
+ public override bool FailureNode => true;
+ ///
+ public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/matches";
+
+ ///
+ /// Gets or sets replacements to replace
+ ///
+ [KeyValue(1, showVariables: true, allowDuplicates: true)]
+ [Required]
+ public List> MatchConditions { get; set; }
+
+ ///
+ public override int Execute(NodeParameters args)
+ {
+ if (MatchConditions?.Any() != true)
+ {
+ args.FailureReason = "No matches defined";
+ args.Logger.ELog(args.FailureReason);
+ return -1;
+ }
+
+ int output = 0;
+ foreach (var match in MatchConditions)
+ {
+ output++;
+ try
+ {
+ var value = args.ReplaceVariables(match.Key, stripMissing: true);
+ if (GeneralHelper.IsRegex(match.Value))
+ {
+ if (Regex.IsMatch(value, match.Value, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
+ {
+ args.Logger?.ILog($"Match found '{match.Value}' = {value}");
+ return output;
+ }
+ }
+
+ if (match.Value == value)
+ {
+ args.Logger?.ILog($"Match found '{match.Value}' = {value}");
+ return output;
+ }
+
+ if (MathHelper.IsMathOperation(match.Value))
+ {
+ if (MathHelper.IsTrue(value, match.Value))
+ {
+ args.Logger?.ILog($"Match found '{match.Value}' = {value}");
+ return output;
+ }
+ }
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ args.Logger?.ILog("No matches found");
+ return MatchConditions.Count + 1;
+ }
+}
diff --git a/BasicNodes/Helpers/GeneralHelper.cs b/BasicNodes/Helpers/GeneralHelper.cs
new file mode 100644
index 00000000..3576c144
--- /dev/null
+++ b/BasicNodes/Helpers/GeneralHelper.cs
@@ -0,0 +1,17 @@
+namespace FileFlows.BasicNodes.Helpers;
+
+///
+/// General helper
+///
+public class GeneralHelper
+{
+ ///
+ /// Checks if the input string represents a regular expression.
+ ///
+ /// The input string to check.
+ /// True if the input is a regular expression, otherwise false.
+ public static bool IsRegex(string input)
+ {
+ return new[] { "?", "|", "^", "$", "*" }.Any(ch => input.Contains(ch));
+ }
+}
\ No newline at end of file
diff --git a/BasicNodes/Helpers/MathHelper.cs b/BasicNodes/Helpers/MathHelper.cs
new file mode 100644
index 00000000..88e00ba5
--- /dev/null
+++ b/BasicNodes/Helpers/MathHelper.cs
@@ -0,0 +1,166 @@
+using System.Globalization;
+
+namespace FileFlows.BasicNodes.Helpers;
+
+///
+/// Helper for math operations
+///
+public class MathHelper
+{
+ ///
+ /// Checks if the comparison string represents a mathematical operation.
+ ///
+ /// The comparison string to check.
+ /// True if the comparison is a mathematical operation, otherwise false.
+ public static bool IsMathOperation(string comparison)
+ {
+ // Check if the comparison string starts with <=, <, >, >=, ==, or =
+ return new[] { "<=", "<", ">", ">=", "==", "=" }.Any(comparison.StartsWith);
+ }
+
+
+ ///
+ /// Tests if a math operation is true
+ ///
+ /// The value to apply the operation to.
+ /// The operation string representing the mathematical operation.
+ /// True if the mathematical operation is successful, otherwise false.
+ public static bool IsTrue(string value, string operation)
+ {
+ // This is a basic example; you may need to handle different operators
+ switch (operation[..2])
+ {
+ case "<=":
+ return Convert.ToDouble(value) <= Convert.ToDouble(AdjustComparisonValue(operation[2..].Trim()));
+ case ">=":
+ return Convert.ToDouble(value) >= Convert.ToDouble(AdjustComparisonValue(operation[2..].Trim()));
+ case "==":
+ return Math.Abs(Convert.ToDouble(value) - Convert.ToDouble(AdjustComparisonValue(operation[2..].Trim()))) < 0.05f;
+ case "!=":
+ case "<>":
+ return Math.Abs(Convert.ToDouble(value) - Convert.ToDouble(AdjustComparisonValue(operation[2..].Trim()))) > 0.05f;
+ }
+
+ switch (operation[..1])
+ {
+ case "<":
+ return Convert.ToDouble(value) < Convert.ToDouble(AdjustComparisonValue(operation[1..].Trim()));
+ case ">":
+ return Convert.ToDouble(value) > Convert.ToDouble(AdjustComparisonValue(operation[1..].Trim()));
+ case "=":
+ return Math.Abs(Convert.ToDouble(value) - Convert.ToDouble(AdjustComparisonValue(operation[1..].Trim()))) < 0.05f;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Adjusts the comparison string by handling common mistakes in units and converting them into full numbers.
+ ///
+ /// The original comparison string to be adjusted.
+ /// The adjusted comparison string with corrected units or the original comparison if no adjustments are made.
+ private static string AdjustComparisonValue(string comparisonValue)
+ {
+ if (string.IsNullOrWhiteSpace(comparisonValue))
+ return string.Empty;
+
+ string adjustedComparison = comparisonValue.ToLower().Trim();
+
+ // Handle common mistakes in units
+ if (adjustedComparison.EndsWith("mbps"))
+ {
+ // Make an educated guess for Mbps to kbps conversion
+ return adjustedComparison[..^4] switch
+ {
+ { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000_000)
+ .ToString(CultureInfo.InvariantCulture),
+ _ => comparisonValue
+ };
+ }
+ if (adjustedComparison.EndsWith("kbps"))
+ {
+ // Make an educated guess for kbps to bps conversion
+ return adjustedComparison[..^4] switch
+ {
+ { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000)
+ .ToString(CultureInfo.InvariantCulture),
+ _ => comparisonValue
+ };
+ }
+ if (adjustedComparison.EndsWith("kb"))
+ {
+ return adjustedComparison[..^2] switch
+ {
+ { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000 )
+ .ToString(CultureInfo.InvariantCulture),
+ _ => comparisonValue
+ };
+ }
+ if (adjustedComparison.EndsWith("mb"))
+ {
+ return adjustedComparison[..^2] switch
+ {
+ { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000_000 )
+ .ToString(CultureInfo.InvariantCulture),
+ _ => comparisonValue
+ };
+ }
+ if (adjustedComparison.EndsWith("gb"))
+ {
+ return adjustedComparison[..^2] switch
+ {
+ { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000_000_000 )
+ .ToString(CultureInfo.InvariantCulture),
+ _ => comparisonValue
+ };
+ }
+ if (adjustedComparison.EndsWith("tb"))
+ {
+ return adjustedComparison[..^2] switch
+ {
+ { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000_000_000_000)
+ .ToString(CultureInfo.InvariantCulture),
+ _ => comparisonValue
+ };
+ }
+
+ if (adjustedComparison.EndsWith("kib"))
+ {
+ return adjustedComparison[..^3] switch
+ {
+ { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_024 )
+ .ToString(CultureInfo.InvariantCulture),
+ _ => comparisonValue
+ };
+ }
+ if (adjustedComparison.EndsWith("mib"))
+ {
+ return adjustedComparison[..^3] switch
+ {
+ { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_048_576 )
+ .ToString(CultureInfo.InvariantCulture),
+ _ => comparisonValue
+ };
+ }
+ if (adjustedComparison.EndsWith("gib"))
+ {
+ return adjustedComparison[..^3] switch
+ {
+ { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_099_511_627_776 )
+ .ToString(CultureInfo.InvariantCulture),
+ _ => comparisonValue
+ };
+ }
+ if (adjustedComparison.EndsWith("tib"))
+ {
+ return adjustedComparison[..^3] switch
+ {
+ { } value when double.TryParse(value, out var numericValue) => (numericValue * 1_000_000_000_000)
+ .ToString(CultureInfo.InvariantCulture),
+ _ => comparisonValue
+ };
+ }
+ return comparisonValue;
+ }
+
+}
\ No newline at end of file
diff --git a/BasicNodes/Tests/MatchesTests.cs b/BasicNodes/Tests/MatchesTests.cs
new file mode 100644
index 00000000..3a29fb95
--- /dev/null
+++ b/BasicNodes/Tests/MatchesTests.cs
@@ -0,0 +1,92 @@
+#if(DEBUG)
+
+using FileFlows.BasicNodes.Functions;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace BasicNodes.Tests;
+
+[TestClass]
+public class MatchesTests
+{
+ [TestMethod]
+ public void Matches_Math()
+ {
+ var logger = new TestLogger();
+ Matches ele = new ();
+ ele.MatchConditions = new()
+ {
+ new("{file.Size}", "<=100KB"),
+ new("{file.Size}", ">100KB"),
+ new("{file.Size}", ">10MB"),
+ };
+ var args = new FileFlows.Plugin.NodeParameters(null, logger,
+ false, string.Empty, new LocalFileService());
+ args.Variables["file.Size"] = 120_000; // 120KB
+
+ var result = ele.Execute(args);
+ var log = logger.ToString();
+ Assert.AreEqual(2, result);
+ }
+
+ [TestMethod]
+ public void Matches_String()
+ {
+ var logger = new TestLogger();
+ Matches ele = new ();
+ ele.MatchConditions = new()
+ {
+ new("{file.Name}", "triggerthis"),
+ new("{file.Name}", "DontTriggerThis"),
+ new("{file.Name}", "TriggerThis"),
+ };
+ var args = new FileFlows.Plugin.NodeParameters(null, logger,
+ false, string.Empty, new LocalFileService());
+ args.Variables["file.Name"] = "TriggerThis";
+
+ var result = ele.Execute(args);
+ var log = logger.ToString();
+ Assert.AreEqual(3, result);
+ }
+ [TestMethod]
+ public void Matches_NoMatch()
+ {
+ var logger = new TestLogger();
+ Matches ele = new ();
+ ele.MatchConditions = new()
+ {
+ new("{file.Name}", "triggerthis"),
+ new("{file.Name}", "DontTriggerThis"),
+ new("{file.Name}", "TriggerThis"),
+ };
+ var args = new FileFlows.Plugin.NodeParameters(null, logger,
+ false, string.Empty, new LocalFileService());
+ args.Variables["file.Name"] = "Nothing";
+
+ var result = ele.Execute(args);
+ var log = logger.ToString();
+ Assert.AreEqual(4, result);
+ }
+
+ [TestMethod]
+ public void Matches_Regex()
+ {
+ var logger = new TestLogger();
+ Matches ele = new ();
+ ele.MatchConditions = new()
+ {
+ new("{file.Name}", "triggerthis"),
+ new("{file.Name}", ".*batman.*"),
+ new("{file.Name}", "TriggerThis"),
+ };
+ var args = new FileFlows.Plugin.NodeParameters(null, logger,
+ false, string.Empty, new LocalFileService());
+ args.Variables["file.Name"] = "Superman vs Batman (2017)";
+
+ var result = ele.Execute(args);
+ var log = logger.ToString();
+ Assert.AreEqual(2, result);
+ }
+}
+
+
+#endif
\ No newline at end of file
diff --git a/BasicNodes/i18n/en.json b/BasicNodes/i18n/en.json
index 79cf6904..355651e0 100644
--- a/BasicNodes/i18n/en.json
+++ b/BasicNodes/i18n/en.json
@@ -212,6 +212,15 @@
"Message": "Message"
}
},
+ "Matches": {
+ "Description": "Compares a set of values and matches conditions to see which output should be called",
+ "Fields": {
+ "MatchConditions": "",
+ "MatchConditionsKey": "Value",
+ "MatchConditionsValue": "Expression",
+ "MatchConditions-Help": "The matches to test which output should be called."
+ }
+ },
"MoveFile": {
"Description": "Moves a file to the destination folder",
"Outputs": {
diff --git a/FileFlows.Plugin.dll b/FileFlows.Plugin.dll
index fa793002..4c7b184f 100644
Binary files a/FileFlows.Plugin.dll and b/FileFlows.Plugin.dll differ
diff --git a/FileFlows.Plugin.pdb b/FileFlows.Plugin.pdb
index e1f43fd7..aca62b35 100644
Binary files a/FileFlows.Plugin.pdb and b/FileFlows.Plugin.pdb differ