diff --git a/code/Infrastructure.Tests/Verticals/DownloadClient/TestDownloadService.cs b/code/Infrastructure.Tests/Verticals/DownloadClient/TestDownloadService.cs index e3bbd3a..ebd173f 100644 --- a/code/Infrastructure.Tests/Verticals/DownloadClient/TestDownloadService.cs +++ b/code/Infrastructure.Tests/Verticals/DownloadClient/TestDownloadService.cs @@ -40,11 +40,11 @@ public class TestDownloadService : DownloadService public override Task ShouldRemoveFromArrQueueAsync(string hash) => Task.FromResult(new StalledResult()); public override Task BlockUnwantedFilesAsync(string hash, BlocklistType blocklistType, ConcurrentBag patterns, ConcurrentBag regexes) => Task.FromResult(new BlockFilesResult()); - - public override Task DeleteDownload(string hash) => Task.CompletedTask; - public override Task?> GetDownloadsToBeCleaned(List categories) => Task.FromResult?>(null); - public override Task?> GetDownloadsToChangeCategory(List categories) => Task.FromResult?>(null); - public override Task CleanDownloads(List downloads, List categoriesToClean, HashSet excludedHashes) => Task.CompletedTask; + public override Task DeleteDownloadAsync(string hash) => Task.CompletedTask; + public override Task CreateCategoryAsync(string name) => Task.CompletedTask; + public override Task?> GetDownloadsToBeCleanedAsync(List categories) => Task.FromResult?>(null); + public override Task?> GetDownloadsToChangeCategoryAsync(List categories) => Task.FromResult?>(null); + public override Task CleanDownloadsAsync(List downloads, List categoriesToClean, HashSet excludedHashes) => Task.CompletedTask; public override Task ChangeCategoryForNoHardLinksAsync(List downloads, HashSet excludedHashes) => Task.CompletedTask; // Expose protected methods for testing diff --git a/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs b/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs index ea48b89..623a59e 100644 --- a/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs +++ b/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs @@ -19,6 +19,8 @@ public sealed class DownloadCleaner : GenericHandler private readonly DownloadCleanerConfig _config; private readonly HashSet _excludedHashes = []; + private static bool _hardLinkCategoryCreated = false; + public DownloadCleaner( ILogger logger, IOptions config, @@ -60,12 +62,18 @@ public sealed class DownloadCleaner : GenericHandler await _downloadService.LoginAsync(); - List? downloadsToBeCleaned = await _downloadService.GetDownloadsToBeCleaned(_config.Categories); + List? downloadsToBeCleaned = await _downloadService.GetDownloadsToBeCleanedAsync(_config.Categories); List? downloadsToChangeCategory = null; if (!string.IsNullOrEmpty(_config.NoHardlinksCategory) && _config.HardlinkCategories?.Count > 0) { - downloadsToChangeCategory = await _downloadService.GetDownloadsToChangeCategory(_config.HardlinkCategories); + if (!_hardLinkCategoryCreated) + { + await _downloadService.CreateCategoryAsync(_config.NoHardlinksCategory); + _hardLinkCategoryCreated = true; + } + + downloadsToChangeCategory = await _downloadService.GetDownloadsToChangeCategoryAsync(_config.HardlinkCategories); } bool hasDownloadsToClean = downloadsToBeCleaned?.Count > 0; @@ -95,7 +103,7 @@ public sealed class DownloadCleaner : GenericHandler if (hasDownloadsToClean) { - await _downloadService.CleanDownloads(downloadsToBeCleaned, _config.Categories, _excludedHashes); + await _downloadService.CleanDownloadsAsync(downloadsToBeCleaned, _config.Categories, _excludedHashes); } else { diff --git a/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeService.cs b/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeService.cs index db9ad08..7db5163 100644 --- a/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/Deluge/DelugeService.cs @@ -196,7 +196,7 @@ public class DelugeService : DownloadService, IDelugeService return result; } - public override async Task?> GetDownloadsToBeCleaned(List categories) + public override async Task?> GetDownloadsToBeCleanedAsync(List categories) { return (await _client.GetStatusForAllTorrents()) ?.Where(x => !string.IsNullOrEmpty(x.Hash)) @@ -206,13 +206,13 @@ public class DelugeService : DownloadService, IDelugeService .ToList(); } - public override Task?> GetDownloadsToChangeCategory(List categories) + public override Task?> GetDownloadsToChangeCategoryAsync(List categories) { throw new NotImplementedException(); } /// - public override async Task CleanDownloads(List downloads, List categoriesToClean, HashSet excludedHashes) + public override async Task CleanDownloadsAsync(List downloads, List categoriesToClean, HashSet excludedHashes) { foreach (TorrentStatus download in downloads) { @@ -266,6 +266,11 @@ public class DelugeService : DownloadService, IDelugeService } } + public override async Task CreateCategoryAsync(string name) + { + throw new NotImplementedException(); + } + public override Task ChangeCategoryForNoHardLinksAsync(List downloads, HashSet excludedHashes) { throw new NotImplementedException(); @@ -273,7 +278,7 @@ public class DelugeService : DownloadService, IDelugeService /// [DryRunSafeguard] - public override async Task DeleteDownload(string hash) + public override async Task DeleteDownloadAsync(string hash) { hash = hash.ToLowerInvariant(); diff --git a/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs b/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs index 25468d2..e5b26c1 100644 --- a/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs @@ -75,20 +75,23 @@ public abstract class DownloadService : IDownloadService ); /// - public abstract Task DeleteDownload(string hash); + public abstract Task DeleteDownloadAsync(string hash); /// - public abstract Task?> GetDownloadsToBeCleaned(List categories); + public abstract Task?> GetDownloadsToBeCleanedAsync(List categories); /// - public abstract Task?> GetDownloadsToChangeCategory(List categories); + public abstract Task?> GetDownloadsToChangeCategoryAsync(List categories); /// - public abstract Task CleanDownloads(List downloads, List categoriesToClean, HashSet excludedHashes); + public abstract Task CleanDownloadsAsync(List downloads, List categoriesToClean, HashSet excludedHashes); /// public abstract Task ChangeCategoryForNoHardLinksAsync(List downloads, HashSet excludedHashes); + /// + public abstract Task CreateCategoryAsync(string name); + protected void ResetStrikesOnProgress(string hash, long downloaded) { if (!_queueCleanerConfig.StalledResetStrikesOnProgress) diff --git a/code/Infrastructure/Verticals/DownloadClient/DummyDownloadService.cs b/code/Infrastructure/Verticals/DownloadClient/DummyDownloadService.cs index a340bbc..a0a942e 100644 --- a/code/Infrastructure/Verticals/DownloadClient/DummyDownloadService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/DummyDownloadService.cs @@ -53,17 +53,17 @@ public class DummyDownloadService : DownloadService throw new NotImplementedException(); } - public override Task?> GetDownloadsToBeCleaned(List categories) + public override Task?> GetDownloadsToBeCleanedAsync(List categories) { throw new NotImplementedException(); } - public override Task?> GetDownloadsToChangeCategory(List categories) + public override Task?> GetDownloadsToChangeCategoryAsync(List categories) { throw new NotImplementedException(); } - public override Task CleanDownloads(List downloads, List categoriesToClean, HashSet excludedHashes) + public override Task CleanDownloadsAsync(List downloads, List categoriesToClean, HashSet excludedHashes) { throw new NotImplementedException(); } @@ -73,7 +73,12 @@ public class DummyDownloadService : DownloadService throw new NotImplementedException(); } - public override Task DeleteDownload(string hash) + public override Task CreateCategoryAsync(string name) + { + throw new NotImplementedException(); + } + + public override Task DeleteDownloadAsync(string hash) { throw new NotImplementedException(); } diff --git a/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs b/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs index 0b90c4c..c83c8e4 100644 --- a/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs @@ -32,13 +32,18 @@ public interface IDownloadService : IDisposable ); /// - /// Fetches all downloads. + /// Fetches all downloads that should be cleaned. /// /// The categories by which to filter the downloads. /// A list of downloads for the provided categories. - Task?> GetDownloadsToBeCleaned(List categories); + Task?> GetDownloadsToBeCleanedAsync(List categories); - Task?> GetDownloadsToChangeCategory(List categories); + /// + /// Fetches all downloads that should have their category changed. + /// + /// The categories by which to filter the downloads. + /// A list of downloads for the provided categories. + Task?> GetDownloadsToChangeCategoryAsync(List categories); /// /// Cleans the downloads. @@ -46,7 +51,7 @@ public interface IDownloadService : IDisposable /// The downloads to clean. /// The categories that should be cleaned. /// The hashes that should not be cleaned. - Task CleanDownloads(List downloads, List categoriesToClean, HashSet excludedHashes); + Task CleanDownloadsAsync(List downloads, List categoriesToClean, HashSet excludedHashes); /// /// Changes the category for downloads that have no hardlinks. @@ -58,5 +63,11 @@ public interface IDownloadService : IDisposable /// /// Deletes a download item. /// - public Task DeleteDownload(string hash); + public Task DeleteDownloadAsync(string hash); + + /// + /// Creates a category. + /// + /// The category name. + public Task CreateCategoryAsync(string name); } \ No newline at end of file diff --git a/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs b/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs index 2753363..df0e3ea 100644 --- a/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs @@ -209,7 +209,7 @@ public class QBitService : DownloadService, IQBitService } /// - public override async Task?> GetDownloadsToBeCleaned(List categories) => + public override async Task?> GetDownloadsToBeCleanedAsync(List categories) => (await _client.GetTorrentListAsync(new() { Filter = TorrentListFilter.Seeding @@ -219,7 +219,7 @@ public class QBitService : DownloadService, IQBitService .Cast() .ToList(); - public override async Task?> GetDownloadsToChangeCategory(List categories) + public override async Task?> GetDownloadsToChangeCategoryAsync(List categories) { return (await _client.GetTorrentListAsync(new() { @@ -231,7 +231,7 @@ public class QBitService : DownloadService, IQBitService } /// - public override async Task CleanDownloads(List downloads, List categoriesToClean, HashSet excludedHashes) + public override async Task CleanDownloadsAsync(List downloads, List categoriesToClean, HashSet excludedHashes) { foreach (TorrentInfo download in downloads) { @@ -299,6 +299,18 @@ public class QBitService : DownloadService, IQBitService } } + public override async Task CreateCategoryAsync(string name) + { + IReadOnlyDictionary? existingCategories = await _client.GetCategoriesAsync(); + + if (existingCategories.Any(x => x.Value.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))) + { + return; + } + + await _client.AddCategoryAsync(name); + } + public override async Task ChangeCategoryForNoHardLinksAsync(List downloads, HashSet excludedHashes) { if (_downloadCleanerConfig.IgnoreRootDir) @@ -380,7 +392,7 @@ public class QBitService : DownloadService, IQBitService /// [DryRunSafeguard] - public override async Task DeleteDownload(string hash) + public override async Task DeleteDownloadAsync(string hash) { await _client.DeleteAsync(hash, deleteDownloadedData: true); } diff --git a/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs b/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs index ab1608d..11bdf37 100644 --- a/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs @@ -183,7 +183,7 @@ public class TransmissionService : DownloadService, ITransmissionService } /// - public override async Task?> GetDownloadsToBeCleaned(List categories) + public override async Task?> GetDownloadsToBeCleanedAsync(List categories) { string[] fields = [ TorrentFields.FILES, @@ -220,13 +220,13 @@ public class TransmissionService : DownloadService, ITransmissionService .ToList(); } - public override Task?> GetDownloadsToChangeCategory(List categories) + public override Task?> GetDownloadsToChangeCategoryAsync(List categories) { throw new NotImplementedException(); } /// - public override async Task CleanDownloads(List downloads, List categoriesToClean, HashSet excludedHashes) + public override async Task CleanDownloadsAsync(List downloads, List categoriesToClean, HashSet excludedHashes) { foreach (TorrentInfo download in downloads) { @@ -288,13 +288,18 @@ public class TransmissionService : DownloadService, ITransmissionService await _notifier.NotifyDownloadCleaned(download.uploadRatio ?? 0, seedingTime, category.Name, result.Reason); } } + + public override async Task CreateCategoryAsync(string name) + { + throw new NotImplementedException(); + } public override Task ChangeCategoryForNoHardLinksAsync(List downloads, HashSet excludedHashes) { throw new NotImplementedException(); } - public override async Task DeleteDownload(string hash) + public override async Task DeleteDownloadAsync(string hash) { TorrentInfo? torrent = await GetTorrentAsync(hash);