diff --git a/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs b/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs index 5b5812c..cfede35 100644 --- a/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs +++ b/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs @@ -1,4 +1,4 @@ -using Common.Exceptions; +using Common.Exceptions; using Microsoft.Extensions.Configuration; namespace Common.Configuration.DownloadCleaner; @@ -17,14 +17,14 @@ public sealed record DownloadCleanerConfig : IJobConfig, IIgnoredDownloadsConfig [ConfigurationKeyName("IGNORED_DOWNLOADS_PATH")] public string? IgnoredDownloadsPath { get; init; } - [ConfigurationKeyName("NO_HL_CATEGORY")] - public string NoHardLinksCategory { get; init; } = ""; + [ConfigurationKeyName("UNLINKED_TARGET_CATEGORY")] + public string UnlinkedTargetCategory { get; init; } = "cleanuperr-unlinked"; + + [ConfigurationKeyName("UNLINKED_IGNORED_ROOT_DIR")] + public string UnlinkedIgnoredRootDir { get; init; } = string.Empty; - [ConfigurationKeyName("NO_HL_IGNORE_ROOT_DIR")] - public bool NoHardLinksIgnoreRootDir { get; init; } - - [ConfigurationKeyName("NO_HL_CATEGORIES")] - public List? NoHardLinksCategories { get; init; } + [ConfigurationKeyName("UNLINKED_CATEGORIES")] + public List? UnlinkedCategories { get; init; } public void Validate() { @@ -45,24 +45,29 @@ public sealed record DownloadCleanerConfig : IJobConfig, IIgnoredDownloadsConfig Categories?.ForEach(x => x.Validate()); - if (string.IsNullOrEmpty(NoHardLinksCategory)) + if (string.IsNullOrEmpty(UnlinkedTargetCategory)) { return; } - if (NoHardLinksCategories?.Count is null or 0) + if (UnlinkedCategories?.Count is null or 0) { - throw new ValidationException("no categories configured"); + throw new ValidationException("no unlinked categories configured"); } - if (NoHardLinksCategories.Contains(NoHardLinksCategory)) + if (UnlinkedCategories.Contains(UnlinkedTargetCategory)) { - throw new ValidationException("NO_HARDLINKS_CATEGORY is present in NO_HARDLINKS_CATEGORIES"); + throw new ValidationException($"{SectionName.ToUpperInvariant()}__UNLINKED_TARGET_CATEGORY should not be present in {SectionName.ToUpperInvariant()}__UNLINKED_CATEGORIES"); } - if (NoHardLinksCategories.Any(string.IsNullOrEmpty)) + if (UnlinkedCategories.Any(string.IsNullOrEmpty)) { - throw new ValidationException("empty hardlink filter category found"); + throw new ValidationException("empty unlinked category filter found"); + } + + if (!string.IsNullOrEmpty(UnlinkedIgnoredRootDir) && !Directory.Exists(UnlinkedIgnoredRootDir)) + { + throw new ValidationException($"{UnlinkedIgnoredRootDir} root directory does not exist"); } } } \ No newline at end of file diff --git a/code/Executable/appsettings.Development.json b/code/Executable/appsettings.Development.json index fff6962..2862e03 100644 --- a/code/Executable/appsettings.Development.json +++ b/code/Executable/appsettings.Development.json @@ -44,16 +44,16 @@ "Name": "tv-sonarr", "MAX_RATIO": -1, "MIN_SEED_TIME": 0, - "MAX_SEED_TIME": -1 + "MAX_SEED_TIME": 240 } ], - "IGNORED_DOWNLOADS_PATH": "../test/data/cleanuperr/ignored_downloads", - "NO_HL_CATEGORY": "nohardlinks", - "NO_HL_IGNORE_ROOT_DIR": false, - "NO_HL_CATEGORIES": [ + "UNLINKED_TARGET_CATEGORY": "cleanuperr-unlinked", + "UNLINKED_IGNORED_ROOT_DIR": "../test/data/qbit-win", + "UNLINKED_CATEGORIES": [ "tv-sonarr", "radarr" - ] + ], + "IGNORED_DOWNLOADS_PATH": "../test/data/cleanuperr/ignored_downloads" }, "DOWNLOAD_CLIENT": "qbittorrent", "qBittorrent": { diff --git a/code/Executable/appsettings.json b/code/Executable/appsettings.json index 5fa1fc7..abbc0f1 100644 --- a/code/Executable/appsettings.json +++ b/code/Executable/appsettings.json @@ -37,6 +37,9 @@ "Enabled": false, "DELETE_PRIVATE": false, "CATEGORIES": [], + "UNLINKED_TARGET_CATEGORY": "cleanuperr-unlinked", + "UNLINKED_IGNORED_ROOT_DIR": "", + "UNLINKED_CATEGORIES": [], "IGNORED_DOWNLOADS_PATH": "" }, "DOWNLOAD_CLIENT": "none", diff --git a/code/Infrastructure.Tests/Verticals/DownloadClient/TestDownloadService.cs b/code/Infrastructure.Tests/Verticals/DownloadClient/TestDownloadService.cs index ed4338f..4f7a2a6 100644 --- a/code/Infrastructure.Tests/Verticals/DownloadClient/TestDownloadService.cs +++ b/code/Infrastructure.Tests/Verticals/DownloadClient/TestDownloadService.cs @@ -46,7 +46,7 @@ public class TestDownloadService : DownloadService public override Task?> GetSeedingDownloads() => Task.FromResult?>(null); public override List? FilterDownloadsToBeCleanedAsync(List? downloads, List categories) => null; public override List? FilterDownloadsToChangeCategoryAsync(List? downloads, List categories) => null; - public override Task CleanDownloadsAsync(List? downloads, List categoriesToClean, HashSet excludedHashes) => Task.CompletedTask; + public override Task CleanDownloadsAsync(List? downloads, List categoriesToClean, HashSet excludedHashes, IReadOnlyList ignoredDownloads) => Task.CompletedTask; public override Task ChangeCategoryForNoHardLinksAsync(List? downloads, HashSet excludedHashes, IReadOnlyList ignoredDownloads) => Task.CompletedTask; // Expose protected methods for testing public new void ResetStrikesOnProgress(string hash, long downloaded) => base.ResetStrikesOnProgress(hash, downloaded); diff --git a/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs b/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs index d69aa7f..cb48674 100644 --- a/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs +++ b/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs @@ -70,17 +70,17 @@ public sealed class DownloadCleaner : GenericHandler List? downloads = await _downloadService.GetSeedingDownloads(); List? downloadsToChangeCategory = null; - if (!string.IsNullOrEmpty(_config.NoHardLinksCategory) && _config.NoHardLinksCategories?.Count > 0) + if (!string.IsNullOrEmpty(_config.UnlinkedTargetCategory) && _config.UnlinkedCategories?.Count > 0) { if (!_hardLinkCategoryCreated) { - _logger.LogDebug("creating category {cat}", _config.NoHardLinksCategory); + _logger.LogDebug("creating category {cat}", _config.UnlinkedTargetCategory); - await _downloadService.CreateCategoryAsync(_config.NoHardLinksCategory); + await _downloadService.CreateCategoryAsync(_config.UnlinkedTargetCategory); _hardLinkCategoryCreated = true; } - downloadsToChangeCategory = _downloadService.FilterDownloadsToChangeCategoryAsync(downloads, _config.NoHardLinksCategories); + downloadsToChangeCategory = _downloadService.FilterDownloadsToChangeCategoryAsync(downloads, _config.UnlinkedCategories); } // wait for the downloads to appear in the arr queue @@ -91,7 +91,7 @@ public sealed class DownloadCleaner : GenericHandler await ProcessArrConfigAsync(_lidarrConfig, InstanceType.Lidarr, true); _logger.LogTrace("looking for downloads to change category"); - await _downloadService.ChangeCategoryForNoHardLinksAsync(downloadsToChangeCategory, _excludedHashes); + await _downloadService.ChangeCategoryForNoHardLinksAsync(downloadsToChangeCategory, _excludedHashes, ignoredDownloads); List? downloadsToClean = _downloadService.FilterDownloadsToBeCleanedAsync(downloads, _config.Categories); @@ -99,7 +99,7 @@ public sealed class DownloadCleaner : GenericHandler downloads = null; _logger.LogTrace("looking for downloads to clean"); - await _downloadService.CleanDownloadsAsync(downloadsToClean, _config.Categories, _excludedHashes); + await _downloadService.CleanDownloadsAsync(downloadsToClean, _config.Categories, _excludedHashes, ignoredDownloads); } protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType) diff --git a/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeClient.cs b/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeClient.cs index 195c08b..d5edfc5 100644 --- a/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeClient.cs +++ b/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeClient.cs @@ -193,4 +193,10 @@ public sealed class DelugeClient return webResponse.Result; } + + public async Task SetTorrentLabel(string hash, string newLabel) + { + // TODO + throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeService.cs b/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeService.cs index 8ee8531..73f7b12 100644 --- a/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeService.cs @@ -301,110 +301,112 @@ public class DelugeService : DownloadService, IDelugeService throw new NotImplementedException(); } - public override async Task ChangeCategoryForNoHardLinksAsync(List? downloads, HashSet excludedHashes) + public override async Task ChangeCategoryForNoHardLinksAsync(List? downloads, HashSet excludedHashes, IReadOnlyList ignoredDownloads) { if (downloads?.Count is null or 0) { return; } - - if (_downloadCleanerConfig.NoHardLinksIgnoreRootDir) - { - downloads - .Cast() - .Select(x => - { - string? firstDir = GetRootWithFirstDirectory(x.DownloadPath); - if (string.IsNullOrEmpty(firstDir)) - { - return string.Empty; - } + throw new NotImplementedException(); - if (firstDir == Path.GetPathRoot(x.DownloadPath)) - { - return string.Empty; - } - - return firstDir; - }) - .Where(x => !string.IsNullOrEmpty(x)) - .Distinct() - .ToList() - .ForEach(x => - { - _logger.LogTrace("populating file counts from {dir}", x); - - if (!Directory.Exists(x)) - { - throw new ValidationException($"directory \"{x}\" does not exist"); - } - - _hardLinkFileService.PopulateFileCounts(x); - }); - } - - foreach (TorrentStatus download in downloads.Cast()) - { - if (string.IsNullOrEmpty(download.Hash)) - { - _logger.LogDebug("skip | download hash is null for {name}", download.Name); - continue; - } - - if (excludedHashes.Any(x => x.Equals(download.Hash, StringComparison.InvariantCultureIgnoreCase))) - { - _logger.LogDebug("skip | download is used by an arr | {name}", download.Name); - continue; - } - - ContextProvider.Set("downloadName", download.Name); - ContextProvider.Set("hash", download.Hash); - - DelugeContents? contents = null; - try - { - contents = await _client.GetTorrentFiles(download.Hash); - } - catch (Exception exception) - { - _logger.LogDebug(exception, "failed to find torrent files for {name}", download.Name); - continue; - } - - bool hasHardlinks = false; - - ProcessFiles(contents?.Contents, (name, file) => - { - string filePath = Path.Combine(download.DownloadPath, file.Path); - - long hardlinkCount = _hardLinkFileService.GetHardLinkCount(filePath, _downloadCleanerConfig.NoHardLinksIgnoreRootDir); - - if (hardlinkCount < 0) - { - _logger.LogDebug("skip | could not get file properties | {name}", download.Name); - hasHardlinks = true; - return; - } - - if (hardlinkCount > 0) - { - hasHardlinks = true; - } - }); - - if (hasHardlinks) - { - _logger.LogDebug("skip | download has hardlinks | {name}", download.Name); - continue; - } - - await _dryRunInterceptor.InterceptAsync(ChangeLabel, download.Hash, _downloadCleanerConfig.NoHardLinksCategory); - - _logger.LogInformation("category changed for {name}", download.Name); - - await _notifier.NotifyCategoryChanged(download.Label, _downloadCleanerConfig.NoHardLinksCategory); - } + // if (_downloadCleanerConfig.NoHardLinksIgnoreRootDir) + // { + // downloads + // .Cast() + // .Select(x => + // { + // string? firstDir = GetRootWithFirstDirectory(x.DownloadPath); + // + // if (string.IsNullOrEmpty(firstDir)) + // { + // return string.Empty; + // } + // + // if (firstDir == Path.GetPathRoot(x.DownloadPath)) + // { + // return string.Empty; + // } + // + // return firstDir; + // }) + // .Where(x => !string.IsNullOrEmpty(x)) + // .Distinct() + // .ToList() + // .ForEach(x => + // { + // _logger.LogTrace("populating file counts from {dir}", x); + // + // if (!Directory.Exists(x)) + // { + // throw new ValidationException($"directory \"{x}\" does not exist"); + // } + // + // _hardLinkFileService.PopulateFileCounts(x); + // }); + // } + // + // foreach (TorrentStatus download in downloads.Cast()) + // { + // if (string.IsNullOrEmpty(download.Hash)) + // { + // _logger.LogDebug("skip | download hash is null for {name}", download.Name); + // continue; + // } + // + // if (excludedHashes.Any(x => x.Equals(download.Hash, StringComparison.InvariantCultureIgnoreCase))) + // { + // _logger.LogDebug("skip | download is used by an arr | {name}", download.Name); + // continue; + // } + // + // ContextProvider.Set("downloadName", download.Name); + // ContextProvider.Set("hash", download.Hash); + // + // DelugeContents? contents = null; + // try + // { + // contents = await _client.GetTorrentFiles(download.Hash); + // } + // catch (Exception exception) + // { + // _logger.LogDebug(exception, "failed to find torrent files for {name}", download.Name); + // continue; + // } + // + // bool hasHardlinks = false; + // + // ProcessFiles(contents?.Contents, (name, file) => + // { + // string filePath = Path.Combine(download.DownloadPath, file.Path); + // + // long hardlinkCount = _hardLinkFileService.GetHardLinkCount(filePath, _downloadCleanerConfig.NoHardLinksIgnoreRootDir); + // + // if (hardlinkCount < 0) + // { + // _logger.LogDebug("skip | could not get file properties | {name}", download.Name); + // hasHardlinks = true; + // return; + // } + // + // if (hardlinkCount > 0) + // { + // hasHardlinks = true; + // } + // }); + // + // if (hasHardlinks) + // { + // _logger.LogDebug("skip | download has hardlinks | {name}", download.Name); + // continue; + // } + // + // await _dryRunInterceptor.InterceptAsync(ChangeLabel, download.Hash, _downloadCleanerConfig.NoHardLinksCategory); + // + // _logger.LogInformation("category changed for {name}", download.Name); + // + // await _notifier.NotifyCategoryChanged(download.Label, _downloadCleanerConfig.NoHardLinksCategory); + // } } /// diff --git a/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs b/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs index f579173..7c410cc 100644 --- a/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs @@ -88,7 +88,7 @@ public abstract class DownloadService : IDownloadService public abstract Task CleanDownloadsAsync(List? downloads, List categoriesToClean, HashSet excludedHashes, IReadOnlyList ignoredDownloads); /// - public abstract Task ChangeCategoryForNoHardLinksAsync(List? downloads, HashSet excludedHashes); + public abstract Task ChangeCategoryForNoHardLinksAsync(List? downloads, HashSet excludedHashes, IReadOnlyList ignoredDownloads); /// public abstract Task CreateCategoryAsync(string name); diff --git a/code/Infrastructure/Verticals/DownloadClient/DummyDownloadService.cs b/code/Infrastructure/Verticals/DownloadClient/DummyDownloadService.cs index 3f4f575..e52b3d9 100644 --- a/code/Infrastructure/Verticals/DownloadClient/DummyDownloadService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/DummyDownloadService.cs @@ -74,7 +74,7 @@ public class DummyDownloadService : DownloadService throw new NotImplementedException(); } - public override Task ChangeCategoryForNoHardLinksAsync(List? downloads, HashSet excludedHashes) + public override Task ChangeCategoryForNoHardLinksAsync(List? downloads, HashSet excludedHashes, IReadOnlyList ignoredDownloads) { throw new NotImplementedException(); } diff --git a/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs b/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs index bed6fea..e73df6c 100644 --- a/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs @@ -61,14 +61,16 @@ public interface IDownloadService : IDisposable /// The downloads to clean. /// The categories that should be cleaned. /// The hashes that should not be cleaned. + /// The downloads to ignore from processing. Task CleanDownloadsAsync(List? downloads, List categoriesToClean, HashSet excludedHashes, IReadOnlyList ignoredDownloads); /// /// Changes the category for downloads that have no hardlinks. /// /// The downloads to change. - /// - Task ChangeCategoryForNoHardLinksAsync(List? downloads, HashSet excludedHashes); + /// The hashes that should not be cleaned. + /// The downloads to ignore from processing. + Task ChangeCategoryForNoHardLinksAsync(List? downloads, HashSet excludedHashes, IReadOnlyList ignoredDownloads); /// /// Deletes a download item. diff --git a/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs b/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs index f28a716..784472b 100644 --- a/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs @@ -271,10 +271,16 @@ public class QBitService : DownloadService, IQBitService continue; } + if (excludedHashes.Any(x => x.Equals(download.Hash, StringComparison.InvariantCultureIgnoreCase))) + { + _logger.LogDebug("skip | download is used by an arr | {name}", download.Name); + continue; + } + IReadOnlyList trackers = await GetTrackersAsync(download.Hash); if (ignoredDownloads.Count > 0 && - (download.ShouldIgnore(ignoredDownloads) || trackers.Any(x => x.ShouldIgnore(ignoredDownloads)) is true)) + (download.ShouldIgnore(ignoredDownloads) || trackers.Any(x => x.ShouldIgnore(ignoredDownloads)))) { _logger.LogInformation("skip | download is ignored | {name}", download.Name); continue; @@ -288,12 +294,6 @@ public class QBitService : DownloadService, IQBitService continue; } - if (excludedHashes.Any(x => x.Equals(download.Hash, StringComparison.InvariantCultureIgnoreCase))) - { - _logger.LogDebug("skip | download is used by an arr | {name}", download.Name); - continue; - } - if (!_downloadCleanerConfig.DeletePrivate) { TorrentProperties? torrentProperties = await _client.GetTorrentPropertiesAsync(download.Hash); @@ -351,57 +351,23 @@ public class QBitService : DownloadService, IQBitService await _client.AddCategoryAsync(name); } - public override async Task ChangeCategoryForNoHardLinksAsync(List? downloads, HashSet excludedHashes) + public override async Task ChangeCategoryForNoHardLinksAsync(List? downloads, HashSet excludedHashes, IReadOnlyList ignoredDownloads) { if (downloads?.Count is null or 0) { return; } - if (_downloadCleanerConfig.NoHardLinksIgnoreRootDir) + if (!string.IsNullOrEmpty(_downloadCleanerConfig.UnlinkedIgnoredRootDir)) { - downloads - .Cast() - .Select(x => - { - string? firstDir = GetRootWithFirstDirectory(x.SavePath); - - if (string.IsNullOrEmpty(firstDir)) - { - return string.Empty; - } - - if (firstDir == Path.GetPathRoot(x.SavePath)) - { - return string.Empty; - } - - return firstDir; - }) - .Where(x => !string.IsNullOrEmpty(x)) - .Distinct() - .ToList() - .ForEach(x => - { - _logger.LogTrace("populating file counts from {dir}", x); - - if (!Directory.Exists(x)) - { - throw new ValidationException($"directory \"{x}\" does not exist"); - } - - _hardLinkFileService.PopulateFileCounts(x); - }); + _hardLinkFileService.PopulateFileCounts(_downloadCleanerConfig.UnlinkedIgnoredRootDir); } foreach (TorrentInfo download in downloads) { - IReadOnlyList? files = await _client.GetTorrentContentsAsync(download.Hash); - - if (files is null) + if (string.IsNullOrEmpty(download.Hash)) { - _logger.LogDebug("failed to find files for {name}", download.Name); - return; + continue; } if (excludedHashes.Any(x => x.Equals(download.Hash, StringComparison.InvariantCultureIgnoreCase))) @@ -409,6 +375,23 @@ public class QBitService : DownloadService, IQBitService _logger.LogDebug("skip | download is used by an arr | {name}", download.Name); continue; } + + IReadOnlyList trackers = await GetTrackersAsync(download.Hash); + + if (ignoredDownloads.Count > 0 && + (download.ShouldIgnore(ignoredDownloads) || trackers.Any(x => x.ShouldIgnore(ignoredDownloads)))) + { + _logger.LogInformation("skip | download is ignored | {name}", download.Name); + continue; + } + + IReadOnlyList? files = await _client.GetTorrentContentsAsync(download.Hash); + + if (files is null) + { + _logger.LogDebug("failed to find files for {name}", download.Name); + continue; + } ContextProvider.Set("downloadName", download.Name); ContextProvider.Set("hash", download.Hash); @@ -419,20 +402,17 @@ public class QBitService : DownloadService, IQBitService if (!file.Index.HasValue) { _logger.LogDebug("skip | file index is null for {name}", download.Name); - return; + hasHardlinks = true; + break; } - var ceva = Path.Combine(download.ContentPath, file.Name); - var ceva2 = Path.Combine(download.SavePath, file.Name); + // string filePath = Path.Combine(Directory.Exists(download.ContentPath) + // ? download.ContentPath + // : download.SavePath, file.Name + // ); + string filePath = string.Join(Path.DirectorySeparatorChar, Path.Combine(download.SavePath, file.Name).Split(['\\', '/'])); // TODO - string filePath = Path.Combine(Directory.Exists(download.ContentPath) - ? download.ContentPath - : download.SavePath, file.Name - ); - filePath = string.Join(Path.DirectorySeparatorChar, Path.Combine(download.SavePath, file.Name).Split(['\\', '/'])); // TODO - - // TODO add config for root directory - long hardlinkCount = _hardLinkFileService.GetHardLinkCount(filePath, _downloadCleanerConfig.NoHardLinksIgnoreRootDir); + long hardlinkCount = _hardLinkFileService.GetHardLinkCount(filePath, !string.IsNullOrEmpty(_downloadCleanerConfig.UnlinkedIgnoredRootDir)); if (hardlinkCount < 0) { @@ -444,6 +424,7 @@ public class QBitService : DownloadService, IQBitService if (hardlinkCount > 0) { hasHardlinks = true; + break; } } @@ -453,13 +434,13 @@ public class QBitService : DownloadService, IQBitService continue; } - await _dryRunInterceptor.InterceptAsync(ChangeCategory, download.Hash, _downloadCleanerConfig.NoHardLinksCategory); + await _dryRunInterceptor.InterceptAsync(ChangeCategory, download.Hash, _downloadCleanerConfig.UnlinkedTargetCategory); _logger.LogInformation("category changed for {name}", download.Name); - await _notifier.NotifyCategoryChanged(download.Category, _downloadCleanerConfig.NoHardLinksCategory); + await _notifier.NotifyCategoryChanged(download.Category, _downloadCleanerConfig.UnlinkedTargetCategory); - download.Category = _downloadCleanerConfig.NoHardLinksCategory; + download.Category = _downloadCleanerConfig.UnlinkedTargetCategory; } } diff --git a/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs b/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs index 3383cad..c86893d 100644 --- a/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs @@ -269,6 +269,11 @@ public class TransmissionService : DownloadService, ITransmissionService public override async Task CleanDownloadsAsync(List? downloads, List categoriesToClean, HashSet excludedHashes, IReadOnlyList ignoredDownloads) { + if (downloads?.Count is null or 0) + { + return; + } + foreach (TorrentInfo download in downloads) { if (string.IsNullOrEmpty(download.HashString)) @@ -341,117 +346,120 @@ public class TransmissionService : DownloadService, ITransmissionService throw new NotImplementedException(); } - public override async Task ChangeCategoryForNoHardLinksAsync(List? downloads, HashSet excludedHashes) + public override async Task ChangeCategoryForNoHardLinksAsync(List? downloads, HashSet excludedHashes, IReadOnlyList ignoredDownloads) { if (downloads?.Count is null or 0) { return; } - if (_downloadCleanerConfig.NoHardLinksIgnoreRootDir) - { - downloads - .Cast() - .Select(x => - { - if (x.DownloadDir == null) - { - return string.Empty; - } - - string? firstDir = GetRootWithFirstDirectory(x.DownloadDir); - - if (string.IsNullOrEmpty(firstDir)) - { - return string.Empty; - } - - if (firstDir == Path.GetPathRoot(x.DownloadDir)) - { - return string.Empty; - } - - return firstDir; - }) - .Where(x => !string.IsNullOrEmpty(x)) - .Distinct() - .ToList() - .ForEach(x => - { - _logger.LogTrace("populating file counts from {dir}", x); - - if (!Directory.Exists(x)) - { - throw new ValidationException($"directory \"{x}\" does not exist"); - } - - _hardLinkFileService.PopulateFileCounts(x); - }); - } + // TODO ignored downloads + throw new NotImplementedException(); - foreach (TorrentInfo download in downloads.Cast()) - { - if (string.IsNullOrEmpty(download.HashString) || download.DownloadDir == null) - { - _logger.LogDebug("skip | download hash or download directory is null for {name}", download.Name); - continue; - } - - if (excludedHashes.Any(x => x.Equals(download.HashString, StringComparison.InvariantCultureIgnoreCase))) - { - _logger.LogDebug("skip | download is used by an arr | {name}", download.Name); - continue; - } - - ContextProvider.Set("downloadName", download.Name); - ContextProvider.Set("hash", download.HashString); - - bool hasHardlinks = false; - - if (download.Files != null) - { - foreach (TransmissionTorrentFiles file in download.Files) - { - string filePath = Path.Combine(download.DownloadDir, file.Name); - - long hardlinkCount = _hardLinkFileService.GetHardLinkCount(filePath, _downloadCleanerConfig.NoHardLinksIgnoreRootDir); - - if (hardlinkCount < 0) - { - _logger.LogDebug("skip | could not get file properties | {name}", download.Name); - hasHardlinks = true; - break; - } - - if (hardlinkCount > 0) - { - hasHardlinks = true; - break; - } - } - } - - if (hasHardlinks) - { - _logger.LogDebug("skip | download has hardlinks | {name}", download.Name); - continue; - } - - // Get the current category (directory name) - string currentCategory = Path.GetFileName(Path.TrimEndingDirectorySeparator(download.DownloadDir)); - - // Create the new location path - string newLocation = Path.Combine( - Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(download.DownloadDir)) ?? string.Empty, - _downloadCleanerConfig.NoHardLinksCategory - ); - - await _dryRunInterceptor.InterceptAsync(MoveDownload, download.Id, newLocation); - - _logger.LogInformation("category changed for {name}", download.Name); - - await _notifier.NotifyCategoryChanged(currentCategory, _downloadCleanerConfig.NoHardLinksCategory); - } + // if (_downloadCleanerConfig.NoHardLinksIgnoreRootDir) + // { + // downloads + // .Cast() + // .Select(x => + // { + // if (x.DownloadDir == null) + // { + // return string.Empty; + // } + // + // string? firstDir = GetRootWithFirstDirectory(x.DownloadDir); + // + // if (string.IsNullOrEmpty(firstDir)) + // { + // return string.Empty; + // } + // + // if (firstDir == Path.GetPathRoot(x.DownloadDir)) + // { + // return string.Empty; + // } + // + // return firstDir; + // }) + // .Where(x => !string.IsNullOrEmpty(x)) + // .Distinct() + // .ToList() + // .ForEach(x => + // { + // _logger.LogTrace("populating file counts from {dir}", x); + // + // if (!Directory.Exists(x)) + // { + // throw new ValidationException($"directory \"{x}\" does not exist"); + // } + // + // _hardLinkFileService.PopulateFileCounts(x); + // }); + // } + // + // foreach (TorrentInfo download in downloads.Cast()) + // { + // if (string.IsNullOrEmpty(download.HashString) || download.DownloadDir == null) + // { + // _logger.LogDebug("skip | download hash or download directory is null for {name}", download.Name); + // continue; + // } + // + // if (excludedHashes.Any(x => x.Equals(download.HashString, StringComparison.InvariantCultureIgnoreCase))) + // { + // _logger.LogDebug("skip | download is used by an arr | {name}", download.Name); + // continue; + // } + // + // ContextProvider.Set("downloadName", download.Name); + // ContextProvider.Set("hash", download.HashString); + // + // bool hasHardlinks = false; + // + // if (download.Files != null) + // { + // foreach (TransmissionTorrentFiles file in download.Files) + // { + // string filePath = Path.Combine(download.DownloadDir, file.Name); + // + // long hardlinkCount = _hardLinkFileService.GetHardLinkCount(filePath, _downloadCleanerConfig.NoHardLinksIgnoreRootDir); + // + // if (hardlinkCount < 0) + // { + // _logger.LogDebug("skip | could not get file properties | {name}", download.Name); + // hasHardlinks = true; + // break; + // } + // + // if (hardlinkCount > 0) + // { + // hasHardlinks = true; + // break; + // } + // } + // } + // + // if (hasHardlinks) + // { + // _logger.LogDebug("skip | download has hardlinks | {name}", download.Name); + // continue; + // } + // + // // Get the current category (directory name) + // string currentCategory = Path.GetFileName(Path.TrimEndingDirectorySeparator(download.DownloadDir)); + // + // // Create the new location path + // string newLocation = Path.Combine( + // Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(download.DownloadDir)) ?? string.Empty, + // _downloadCleanerConfig.NoHardLinksCategory + // ); + // + // await _dryRunInterceptor.InterceptAsync(MoveDownload, download.Id, newLocation); + // + // _logger.LogInformation("category changed for {name}", download.Name); + // + // await _notifier.NotifyCategoryChanged(currentCategory, _downloadCleanerConfig.NoHardLinksCategory); + // } } [DryRunSafeguard] diff --git a/code/Infrastructure/Verticals/Files/HardLinkFileService.cs b/code/Infrastructure/Verticals/Files/HardLinkFileService.cs index c20da36..0f4c3e1 100644 --- a/code/Infrastructure/Verticals/Files/HardLinkFileService.cs +++ b/code/Infrastructure/Verticals/Files/HardLinkFileService.cs @@ -22,6 +22,8 @@ public class HardLinkFileService : IHardLinkFileService public void PopulateFileCounts(string directoryPath) { + _logger.LogTrace("populating file counts from {dir}", directoryPath); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { _windowsHardLinkFileService.PopulateFileCounts(directoryPath); diff --git a/code/Infrastructure/Verticals/Files/UnixHardLinkFileService.cs b/code/Infrastructure/Verticals/Files/UnixHardLinkFileService.cs index 3af570c..ebe6920 100644 --- a/code/Infrastructure/Verticals/Files/UnixHardLinkFileService.cs +++ b/code/Infrastructure/Verticals/Files/UnixHardLinkFileService.cs @@ -51,15 +51,11 @@ public class UnixHardLinkFileService : IHardLinkFileService, IDisposable { try { - foreach (var file in Directory.EnumerateFiles(directoryPath, "*", SearchOption.AllDirectories)) + // traverse all files in the ignored path and subdirectories + foreach (string file in Directory.EnumerateFiles(directoryPath, "*", SearchOption.AllDirectories)) { AddInodeToCount(file); } - - foreach (var dir in Directory.EnumerateDirectories(directoryPath, "*", SearchOption.AllDirectories)) - { - AddInodeToCount(dir); - } } catch (Exception ex) { diff --git a/code/Infrastructure/Verticals/Files/WindowsHardLinkFileService.cs b/code/Infrastructure/Verticals/Files/WindowsHardLinkFileService.cs index 5fd9e97..70dd1ff 100644 --- a/code/Infrastructure/Verticals/Files/WindowsHardLinkFileService.cs +++ b/code/Infrastructure/Verticals/Files/WindowsHardLinkFileService.cs @@ -57,16 +57,11 @@ public class WindowsHardLinkFileService : IHardLinkFileService, IDisposable { try { - // Traverse all files and directories in the ignored path - foreach (var file in Directory.EnumerateFiles(directoryPath, "*", SearchOption.AllDirectories)) + // traverse all files in the ignored path and subdirectories + foreach (string file in Directory.EnumerateFiles(directoryPath, "*", SearchOption.AllDirectories)) { AddFileIndexToCount(file); } - - foreach (var dir in Directory.EnumerateDirectories(directoryPath, "*", SearchOption.AllDirectories)) - { - AddFileIndexToCount(dir); - } } catch (Exception ex) {