refactored names; fixed return values for hard links service

This commit is contained in:
Flaminel
2025-02-22 00:30:42 +02:00
parent 9b68792ea9
commit 1ad07b1f51
15 changed files with 115 additions and 95 deletions
@@ -0,0 +1,49 @@
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
namespace Infrastructure.Verticals.Files;
public class HardLinkFileService : IHardLinkFileService
{
private readonly ILogger<HardLinkFileService> _logger;
private readonly UnixHardLinkFileService _unixHardLinkFileService;
private readonly WindowsHardLinkFileService _windowsHardLinkFileService;
public HardLinkFileService(
ILogger<HardLinkFileService> logger,
UnixHardLinkFileService unixHardLinkFileService,
WindowsHardLinkFileService windowsHardLinkFileService
)
{
_logger = logger;
_unixHardLinkFileService = unixHardLinkFileService;
_windowsHardLinkFileService = windowsHardLinkFileService;
}
public void PopulateFileCounts(string directoryPath)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_windowsHardLinkFileService.PopulateFileCounts(directoryPath);
return;
}
_unixHardLinkFileService.PopulateFileCounts(directoryPath);
}
public long GetHardLinkCount(string filePath, bool ignoreRootDir)
{
if (!File.Exists(filePath))
{
_logger.LogDebug("file {file} does not exist", filePath);
return -1;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return _windowsHardLinkFileService.GetHardLinkCount(filePath, ignoreRootDir);
}
return _unixHardLinkFileService.GetHardLinkCount(filePath, ignoreRootDir);
}
}
@@ -1,49 +0,0 @@
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
namespace Infrastructure.Verticals.Files;
public class HardlinkFileService : IHardlinkFileService
{
private readonly ILogger<HardlinkFileService> _logger;
private readonly UnixHardlinkFileService _unixHardlinkFileService;
private readonly WindowsHardlinkFileService _windowsHardlinkFileService;
public HardlinkFileService(
ILogger<HardlinkFileService> logger,
UnixHardlinkFileService unixHardlinkFileService,
WindowsHardlinkFileService windowsHardlinkFileService
)
{
_logger = logger;
_unixHardlinkFileService = unixHardlinkFileService;
_windowsHardlinkFileService = windowsHardlinkFileService;
}
public void PopulateInodeCounts(string directoryPath)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_windowsHardlinkFileService.PopulateFileIndexCounts(directoryPath);
return;
}
_unixHardlinkFileService.PopulateInodeCounts(directoryPath);
}
public long GetHardLinkCount(string filePath, bool ignoreRootDir)
{
if (!File.Exists(filePath))
{
_logger.LogDebug("file {file} does not exist", filePath);
return -1;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return _windowsHardlinkFileService.GetWindowsHardLinkCount(filePath, ignoreRootDir);
}
return _unixHardlinkFileService.GetHardlinkCount(filePath, ignoreRootDir);
}
}
@@ -0,0 +1,19 @@
namespace Infrastructure.Verticals.Files;
public interface IHardLinkFileService
{
/// <summary>
/// Populates the inode counts for Unix and the file index counts for Windows.
/// Needs to be called before <see cref="GetHardLinkCount"/> to populate the inode counts.
/// </summary>
/// <param name="directoryPath">The root directory where to search for hardlinks.</param>
void PopulateFileCounts(string directoryPath);
/// <summary>
/// Get the hardlink count of a file.
/// </summary>
/// <param name="filePath">File path.</param>
/// <param name="ignoreRootDir">Whether to ignore hardlinks found in the same root dir.</param>
/// <returns>-1 on error, 0 if there are no hardlinks and 1 otherwise.</returns>
long GetHardLinkCount(string filePath, bool ignoreRootDir);
}
@@ -1,7 +0,0 @@
namespace Infrastructure.Verticals.Files;
public interface IHardlinkFileService
{
void PopulateInodeCounts(string directoryPath);
long GetHardLinkCount(string filePath, bool ignoreRootDir);
}
@@ -4,18 +4,18 @@ using Mono.Unix.Native;
namespace Infrastructure.Verticals.Files;
public class UnixHardlinkFileService
public class UnixHardLinkFileService : IHardLinkFileService
{
private readonly ILogger<UnixHardlinkFileService> _logger;
// Track inode counts in the ignored directory (e.g., root directory)
private readonly ILogger<UnixHardLinkFileService> _logger;
private readonly ConcurrentDictionary<ulong, int> _inodeCounts = new();
public UnixHardlinkFileService(ILogger<UnixHardlinkFileService> logger)
public UnixHardLinkFileService(ILogger<UnixHardLinkFileService> logger)
{
_logger = logger;
}
public long GetHardlinkCount(string filePath, bool ignoreRootDir)
/// <inheritdoc/>
public long GetHardLinkCount(string filePath, bool ignoreRootDir)
{
try
{
@@ -27,15 +27,14 @@ public class UnixHardlinkFileService
if (!ignoreRootDir)
{
// Simple case: Just check if >1 hardlink exists
_logger.LogDebug("stat file | hardlinks: {nlink} | {file}", stat.st_nlink, filePath);
return (long)stat.st_nlink;
return (long)stat.st_nlink == 1 ? 0 : 1;
}
// Adjusted case: Subtract links from the ignored directory
// subtract the number of hardlinks in the same root directory
int linksInIgnoredDir = _inodeCounts.TryGetValue(stat.st_ino, out int count)
? count
: 1; // Default to 1 if not found
? count
: 1; // default to 1 if not found
_logger.LogDebug("stat file | hardlinks: {nlink} | ignored: {ignored} | {file}", stat.st_nlink, linksInIgnoredDir, filePath);
return (long)stat.st_nlink - linksInIgnoredDir;
@@ -47,12 +46,11 @@ public class UnixHardlinkFileService
}
}
// Call this first to populate inode counts from the directory you want to ignore
public void PopulateInodeCounts(string directoryPath)
/// <inheritdoc/>
public void PopulateFileCounts(string directoryPath)
{
try
{
// Traverse all files and directories in the ignored path
foreach (var file in Directory.EnumerateFiles(directoryPath, "*", SearchOption.AllDirectories))
{
AddInodeToCount(file);
@@ -65,7 +63,8 @@ public class UnixHardlinkFileService
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to populate inode counts from {dir}", directoryPath);
_logger.LogError(ex, "failed to populate inode counts from {dir}", directoryPath);
throw;
}
}
@@ -80,7 +79,8 @@ public class UnixHardlinkFileService
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Couldn't stat {path} during inode counting", path);
_logger.LogWarning(ex, "could not stat {path} during inode counting", path);
throw;
}
}
}
@@ -5,18 +5,18 @@ using Microsoft.Win32.SafeHandles;
namespace Infrastructure.Verticals.Files;
public class WindowsHardlinkFileService
public class WindowsHardLinkFileService : IHardLinkFileService
{
private readonly ILogger<WindowsHardlinkFileService> _logger;
// Track file indices in the ignored directory (e.g., root directory)
private readonly ILogger<WindowsHardLinkFileService> _logger;
private readonly ConcurrentDictionary<ulong, int> _fileIndexCounts = new();
public WindowsHardlinkFileService(ILogger<WindowsHardlinkFileService> logger)
public WindowsHardLinkFileService(ILogger<WindowsHardLinkFileService> logger)
{
_logger = logger;
}
public long GetWindowsHardLinkCount(string filePath, bool ignoreRootDir)
/// <inheritdoc/>
public long GetHardLinkCount(string filePath, bool ignoreRootDir)
{
try
{
@@ -31,7 +31,7 @@ public class WindowsHardlinkFileService
if (!ignoreRootDir)
{
_logger.LogDebug("stat file | hardlinks: {nlink} | {file}", file.NumberOfLinks, filePath);
return file.NumberOfLinks;
return file.NumberOfLinks == 1 ? 0 : 1;
}
// Get unique file ID (combination of high and low indices)
@@ -52,7 +52,8 @@ public class WindowsHardlinkFileService
}
}
public void PopulateFileIndexCounts(string directoryPath)
/// <inheritdoc/>
public void PopulateFileCounts(string directoryPath)
{
try
{