refactored names; fixed return values for hard links service
This commit is contained in:
@@ -86,7 +86,7 @@ public sealed class DownloadCleaner : GenericHandler
|
||||
|
||||
if (hasDownloadsToChange)
|
||||
{
|
||||
await _downloadService.ChangeCategoryForNoHardlinksAsync(downloadsToChangeCategory, _excludedHashes);
|
||||
await _downloadService.ChangeCategoryForNoHardLinksAsync(downloadsToChangeCategory, _excludedHashes);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -266,7 +266,7 @@ public class DelugeService : DownloadService, IDelugeService
|
||||
}
|
||||
}
|
||||
|
||||
public override Task ChangeCategoryForNoHardlinksAsync(List<object> downloads, HashSet<string> excludedHashes)
|
||||
public override Task ChangeCategoryForNoHardLinksAsync(List<object> downloads, HashSet<string> excludedHashes)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ public abstract class DownloadService : IDownloadService
|
||||
public abstract Task CleanDownloads(List<object> downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract Task ChangeCategoryForNoHardlinksAsync(List<object> downloads, HashSet<string> excludedHashes);
|
||||
public abstract Task ChangeCategoryForNoHardLinksAsync(List<object> downloads, HashSet<string> excludedHashes);
|
||||
|
||||
protected void ResetStrikesOnProgress(string hash, long downloaded)
|
||||
{
|
||||
|
||||
@@ -68,7 +68,7 @@ public class DummyDownloadService : DownloadService
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task ChangeCategoryForNoHardlinksAsync(List<object> downloads, HashSet<string> excludedHashes)
|
||||
public override Task ChangeCategoryForNoHardLinksAsync(List<object> downloads, HashSet<string> excludedHashes)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public interface IDownloadService : IDisposable
|
||||
/// </summary>
|
||||
/// <param name="downloads">The downloads to change.</param>
|
||||
/// <param name="excludedHashes"></param>
|
||||
Task ChangeCategoryForNoHardlinksAsync(List<object> downloads, HashSet<string> excludedHashes);
|
||||
Task ChangeCategoryForNoHardLinksAsync(List<object> downloads, HashSet<string> excludedHashes);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a download item.
|
||||
|
||||
@@ -5,6 +5,7 @@ using Common.Configuration.ContentBlocker;
|
||||
using Common.Configuration.DownloadCleaner;
|
||||
using Common.Configuration.DownloadClient;
|
||||
using Common.Configuration.QueueCleaner;
|
||||
using Common.Exceptions;
|
||||
using Common.Helpers;
|
||||
using Domain.Enums;
|
||||
using Infrastructure.Interceptors;
|
||||
@@ -298,20 +299,26 @@ public class QBitService : DownloadService, IQBitService
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task ChangeCategoryForNoHardlinksAsync(List<object> downloads, HashSet<string> excludedHashes)
|
||||
public override async Task ChangeCategoryForNoHardLinksAsync(List<object> downloads, HashSet<string> excludedHashes)
|
||||
{
|
||||
if (_downloadCleanerConfig.IgnoreRootDir)
|
||||
{
|
||||
// TODO call this only if Unix
|
||||
downloads
|
||||
.Cast<TorrentInfo>()
|
||||
.GroupBy(x => x.SavePath)
|
||||
.GroupBy(x => Path.GetPathRoot(x.SavePath))
|
||||
.Select(x => x.Key)
|
||||
.ToList()
|
||||
.ForEach(x => _hardlinkFileService.PopulateInodeCounts(x));
|
||||
.ForEach(x =>
|
||||
{
|
||||
if (!Directory.Exists(x))
|
||||
{
|
||||
throw new ValidationException($"directory \"{x}\" does not exist");
|
||||
}
|
||||
|
||||
_hardLinkFileService.PopulateFileCounts(x);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO account for cross-seed
|
||||
foreach (TorrentInfo download in downloads)
|
||||
{
|
||||
IReadOnlyList<TorrentContent>? files = await _client.GetTorrentContentsAsync(download.Hash);
|
||||
@@ -343,7 +350,7 @@ public class QBitService : DownloadService, IQBitService
|
||||
: download.SavePath, file.Name
|
||||
);
|
||||
|
||||
long hardlinkCount = _hardlinkFileService.GetHardLinkCount(filePath, _downloadCleanerConfig.IgnoreRootDir);
|
||||
long hardlinkCount = _hardLinkFileService.GetHardLinkCount(filePath, _downloadCleanerConfig.IgnoreRootDir);
|
||||
|
||||
if (hardlinkCount < 0)
|
||||
{
|
||||
@@ -364,7 +371,7 @@ public class QBitService : DownloadService, IQBitService
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogInformation("no hardlinks found | changing category for {name}", download.Name);
|
||||
_logger.LogInformation("changing category for {name}", download.Name);
|
||||
|
||||
await ((QBitService)Proxy).ChangeCategory(download.Hash, _downloadCleanerConfig.NoHardlinksCategory);
|
||||
await _notifier.NotifyCategoryChanged(download.Category, _downloadCleanerConfig.NoHardlinksCategory);
|
||||
|
||||
@@ -289,7 +289,7 @@ public class TransmissionService : DownloadService, ITransmissionService
|
||||
}
|
||||
}
|
||||
|
||||
public override Task ChangeCategoryForNoHardlinksAsync(List<object> downloads, HashSet<string> excludedHashes)
|
||||
public override Task ChangeCategoryForNoHardLinksAsync(List<object> downloads, HashSet<string> excludedHashes)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
+15
-15
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-7
@@ -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
|
||||
{
|
||||
Reference in New Issue
Block a user