FF-1347 - added has hard links

This commit is contained in:
John Andrews
2024-02-19 13:36:59 +13:00
parent 3991c3c59e
commit ccafc2b059
4 changed files with 257 additions and 7 deletions

View File

@@ -101,7 +101,18 @@
},
"Fields": {
"FileName": "File Name",
"FileName-Help": "The file to check if exists. This should be used with a variable from a previous node."
"FileName-Help": "The file to check. Leave empty to check the current working file."
}
},
"HasHardLinks": {
"Description": "Checks if a file has hard links to it or not",
"Outputs": {
"1": "Hard links detected",
"2": "No hard links detected"
},
"Fields": {
"FileName": "File Name",
"FileName-Help": "The file to check. Leave empty to check the current working file."
}
},
"Delete": {

View File

@@ -1,27 +1,42 @@
namespace FileFlows.BasicNodes.File;
using FileFlows.Plugin;
using FileFlows.Plugin.Attributes;
namespace FileFlows.BasicNodes.File;
/// <summary>
/// Checks if a file exists
/// </summary>
public class FileExists: Node
{
/// <inheritdoc />
public override int Inputs => 1;
/// <inheritdoc />
public override int Outputs => 2;
/// <inheritdoc />
public override FlowElementType Type => FlowElementType.Logic;
/// <inheritdoc />
public override string Icon => "fas fa-question-circle";
/// <inheritdoc />
public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/file-exists";
/// <inheritdoc />
public override bool NoEditorOnAdd => true;
/// <summary>
/// Gets or sets the name of the file to check
/// Leave blank to test the working file
/// </summary>
[TextVariable(1)]
public string FileName { get; set; }
/// <inheritdoc />
public override int Execute(NodeParameters args)
{
string file = args.ReplaceVariables(FileName ?? string.Empty, true);
string file = args.ReplaceVariables(FileName ?? string.Empty, true)?.EmptyAsNull() ?? args.WorkingFile;
if(string.IsNullOrWhiteSpace(file))
{
args.Logger?.ELog("FileName not set");
args.FailureReason = "FileName not set";
args.Logger?.ELog(args.FailureReason);
return -1;
}
try
@@ -37,7 +52,8 @@ public class FileExists: Node
}
catch (Exception ex)
{
args.Logger?.ELog($"Failed testing if file '{file}' exists: " + ex.Message);
args.FailureReason = $"Failed testing if file '{file}' exists: " + ex.Message;
args.Logger?.ELog(args.FailureReason);
return -1;
}
}

View File

@@ -0,0 +1,187 @@
using System.Diagnostics;
using System.IO;
using FileFlows.Plugin;
using FileFlows.Plugin.Attributes;
namespace FileFlows.BasicNodes.File;
/// <summary>
/// Checks if a file has hard links
/// </summary>
public class HasHardLinks: Node
{
/// <inheritdoc />
public override int Inputs => 1;
/// <inheritdoc />
public override int Outputs => 2;
/// <inheritdoc />
public override FlowElementType Type => FlowElementType.Logic;
/// <inheritdoc />
public override string Icon => "fas fa-link";
/// <inheritdoc />
public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/has-hard-links";
/// <inheritdoc />
public override bool NoEditorOnAdd => true;
/// <summary>
/// Gets or sets the name of the file to check
/// Leave blank to test the working file
/// </summary>
[TextVariable(1)]
public string FileName { get; set; }
/// <inheritdoc />
public override int Execute(NodeParameters args)
{
string file = args.ReplaceVariables(FileName ?? string.Empty, true)?.EmptyAsNull() ?? args.WorkingFile;
if(string.IsNullOrWhiteSpace(file))
{
args.FailureReason = "FileName not set";
args.Logger?.ELog(args.FailureReason);
return -1;
}
bool hasHardLinks = false;
if (OperatingSystem.IsWindows())
hasHardLinks = HasHardLinkWindows(args, file);
else if(OperatingSystem.IsLinux())
hasHardLinks = HasHardLinkLinux(args, file);
else if(OperatingSystem.IsMacOS())
hasHardLinks = HasHardLinkMacOS(args, file);
else
{
args.Logger?.WLog("Unable to determine operating system to check for hard links");
}
return hasHardLinks ? 1 : 2;
}
/// <summary>
/// Checks if a file has hard links on Windows.
/// </summary>
/// <param name="args">Node parameters containing logger.</param>
/// <param name="file">File path to check for hard links.</param>
/// <returns>True if the file has hard links; otherwise, false.</returns>
bool HasHardLinkWindows(NodeParameters args, string file)
{
try
{
// Get file attributes
FileAttributes attributes = System.IO.File.GetAttributes(file);
// Check if the file has the ReparsePoint or Directory flags set
// A file with these flags set may have hard links
if ((attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint ||
(attributes & FileAttributes.Directory) == FileAttributes.Directory)
{
args.Logger?.ILog("The file may have hard links.");
return true;
}
args.Logger?.ILog("The file does not appear to have hard links.");
return false;
}
catch (Exception ex)
{
args.Logger?.WLog("Failed to test if file has hard links: " + ex.Message);
return false;
}
}
/// <summary>
/// Checks if a file has hard links on Linux.
/// </summary>
/// <param name="args">Node parameters containing logger.</param>
/// <param name="file">File path to check for hard links.</param>
/// <returns>True if the file has hard links; otherwise, false.</returns>
bool HasHardLinkLinux(NodeParameters args, string file)
{
try
{
Process process = new Process();
process.StartInfo.FileName = "stat";
process.StartInfo.ArgumentList.Add("-c");
process.StartInfo.ArgumentList.Add("%h");
process.StartInfo.ArgumentList.Add(file);
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.Start();
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
process.WaitForExit();
int linkCount;
if (int.TryParse(output.Trim(), out linkCount))
{
if (linkCount > 1)
{
args.Logger?.ILog("The file has hard links.");
return true;
}
args.Logger?.ILog("The file does not have hard links.");
return false;
}
if(string.IsNullOrWhiteSpace(error))
args.Logger?.ELog(error);
args.Logger?.ILog("Failed to retrieve link count.");
return false;
}
catch (Exception ex)
{
args.Logger?.ELog($"An error occurred: {ex.Message}");
return false;
}
}
/// <summary>
/// Checks if a file has hard links on macOS.
/// </summary>
/// <param name="args">Node parameters containing logger.</param>
/// <param name="file">File path to check for hard links.</param>
/// <returns>True if the file has hard links; otherwise, false.</returns>
public bool HasHardLinkMacOS(NodeParameters args, string file)
{
try
{
Process process = new Process();
process.StartInfo.FileName = "stat";
process.StartInfo.ArgumentList.Add("-f");
process.StartInfo.ArgumentList.Add("%l");
process.StartInfo.ArgumentList.Add(file);
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
int linkCount;
if (int.TryParse(output.Trim(), out linkCount))
{
if (linkCount > 1)
{
args.Logger?.ILog("The file has hard links.");
return true;
}
args.Logger?.ILog("The file does not have hard links.");
return false;
}
args.Logger?.ILog("Failed to retrieve link count.");
return false;
}
catch (Exception ex)
{
args.Logger?.ELog($"An error occurred: {ex.Message}");
return false;
}
}
}

View File

@@ -0,0 +1,36 @@
#if(DEBUG)
namespace BasicNodes.Tests;
using FileFlows.BasicNodes.File;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class HasHardLinksTest
{
[TestMethod]
public void HasHardLink()
{
var logger = new TestLogger();
var args = new FileFlows.Plugin.NodeParameters(@"/home/john/temp/test.file", logger, false, string.Empty, new LocalFileService());;
HasHardLinks element = new ();
var result = element.Execute(args);
Assert.AreEqual(1, result);
}
[TestMethod]
public void NoHardLinks()
{
var logger = new TestLogger();
var args = new FileFlows.Plugin.NodeParameters(@"/home/john/temp/other.test", logger, false, string.Empty, new LocalFileService());;
HasHardLinks element = new ();
var result = element.Execute(args);
Assert.AreEqual(2, result);
}
}
#endif