streamlined downloads processing after category changed

This commit is contained in:
Flaminel
2025-02-26 23:39:08 +02:00
parent 6b33075a21
commit 1243da3d22
8 changed files with 91 additions and 85 deletions
@@ -40,12 +40,12 @@ public class TestDownloadService : DownloadService
public override Task<StalledResult> ShouldRemoveFromArrQueueAsync(string hash) => Task.FromResult(new StalledResult()); public override Task<StalledResult> ShouldRemoveFromArrQueueAsync(string hash) => Task.FromResult(new StalledResult());
public override Task<BlockFilesResult> BlockUnwantedFilesAsync(string hash, BlocklistType blocklistType, public override Task<BlockFilesResult> BlockUnwantedFilesAsync(string hash, BlocklistType blocklistType,
ConcurrentBag<string> patterns, ConcurrentBag<Regex> regexes) => Task.FromResult(new BlockFilesResult()); ConcurrentBag<string> patterns, ConcurrentBag<Regex> regexes) => Task.FromResult(new BlockFilesResult());
public override Task DeleteDownloadAsync(string hash) => Task.CompletedTask; public override Task DeleteDownload(string hash) => Task.CompletedTask;
public override Task CreateCategoryAsync(string name) => Task.CompletedTask; public override Task CreateCategoryAsync(string name) => Task.CompletedTask;
public override Task<List<object>?> GetDownloadsToBeCleanedAsync(List<CleanCategory> categories) => Task.FromResult<List<object>?>(null); public override List<object>? FilterDownloadsToBeCleanedAsync(List<object>? downloads, List<CleanCategory> categories) => Task.FromResult<List<object>?>(null);
public override Task<List<object>?> GetDownloadsToChangeCategoryAsync(List<string> categories) => Task.FromResult<List<object>?>(null); public override List<object>? FilterDownloadsToChangeCategoryAsync(List<object>? downloads, List<string> categories) => Task.FromResult<List<object>?>(null);
public override Task CleanDownloadsAsync(List<object> downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes) => Task.CompletedTask; public override Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes) => Task.CompletedTask;
public override Task ChangeCategoryForNoHardLinksAsync(List<object> downloads, HashSet<string> excludedHashes) => Task.CompletedTask; public override Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes) => Task.CompletedTask;
// Expose protected methods for testing // Expose protected methods for testing
public new void ResetStrikesOnProgress(string hash, long downloaded) => base.ResetStrikesOnProgress(hash, downloaded); public new void ResetStrikesOnProgress(string hash, long downloaded) => base.ResetStrikesOnProgress(hash, downloaded);
@@ -61,31 +61,20 @@ public sealed class DownloadCleaner : GenericHandler
} }
await _downloadService.LoginAsync(); await _downloadService.LoginAsync();
List<object>? downloads = await _downloadService.GetSeedingDownloads();
List<object>? downloadsToBeCleaned = await _downloadService.GetDownloadsToBeCleanedAsync(_config.Categories);
List<object>? downloadsToChangeCategory = null; List<object>? downloadsToChangeCategory = null;
if (!string.IsNullOrEmpty(_config.NoHardLinksCategory) && _config.NoHardLinksCategories?.Count > 0) if (!string.IsNullOrEmpty(_config.NoHardLinksCategory) && _config.NoHardLinksCategories?.Count > 0)
{ {
if (!_hardLinkCategoryCreated) if (!_hardLinkCategoryCreated)
{ {
_logger.LogTrace("creating category {cat}", _config.NoHardLinksCategory); _logger.LogDebug("creating category {cat}", _config.NoHardLinksCategory);
await _downloadService.CreateCategoryAsync(_config.NoHardLinksCategory); await _downloadService.CreateCategoryAsync(_config.NoHardLinksCategory);
_hardLinkCategoryCreated = true; _hardLinkCategoryCreated = true;
} }
_logger.LogTrace("getting downloads to change category"); _downloadService.FilterDownloadsToChangeCategoryAsync(downloads, _config.NoHardLinksCategories);
downloadsToChangeCategory = await _downloadService.GetDownloadsToChangeCategoryAsync(_config.NoHardLinksCategories);
}
bool hasDownloadsToClean = downloadsToBeCleaned?.Count > 0;
bool hasDownloadsToChange = downloadsToChangeCategory?.Count > 0;
if (!hasDownloadsToClean && !hasDownloadsToChange)
{
_logger.LogDebug("no downloads to process");
return;
} }
// wait for the downloads to appear in the arr queue // wait for the downloads to appear in the arr queue
@@ -95,25 +84,13 @@ public sealed class DownloadCleaner : GenericHandler
await ProcessArrConfigAsync(_radarrConfig, InstanceType.Radarr, true); await ProcessArrConfigAsync(_radarrConfig, InstanceType.Radarr, true);
await ProcessArrConfigAsync(_lidarrConfig, InstanceType.Lidarr, true); await ProcessArrConfigAsync(_lidarrConfig, InstanceType.Lidarr, true);
if (hasDownloadsToChange) _logger.LogTrace("looking for downloads to change category");
{ await _downloadService.ChangeCategoryForNoHardLinksAsync(downloadsToChangeCategory, _excludedHashes);
_logger.LogTrace("processing downloads to change category");
await _downloadService.ChangeCategoryForNoHardLinksAsync(downloadsToChangeCategory, _excludedHashes);
}
else
{
_logger.LogTrace("no downloads found to change category");
}
if (hasDownloadsToClean) List<object>? downloadsToClean = _downloadService.FilterDownloadsToBeCleanedAsync(downloads, _config.Categories);
{
_logger.LogTrace("processing downloads to be cleaned"); _logger.LogTrace("looking for downloads clean");
await _downloadService.CleanDownloadsAsync(downloadsToBeCleaned, _config.Categories, _excludedHashes); await _downloadService.CleanDownloadsAsync(downloadsToClean, _config.Categories, _excludedHashes);
}
else
{
_logger.LogTrace("no downloads found to be cleaned");
}
} }
protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType) protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType)
@@ -196,7 +196,7 @@ public class DelugeService : DownloadService, IDelugeService
return result; return result;
} }
public override async Task<List<object>?> GetDownloadsToBeCleanedAsync(List<CleanCategory> categories) public override async List<object>? FilterDownloadsToBeCleanedAsync(List<object>? downloads, List<CleanCategory> categories)
{ {
return (await _client.GetStatusForAllTorrents()) return (await _client.GetStatusForAllTorrents())
?.Where(x => !string.IsNullOrEmpty(x.Hash)) ?.Where(x => !string.IsNullOrEmpty(x.Hash))
@@ -206,13 +206,14 @@ public class DelugeService : DownloadService, IDelugeService
.ToList(); .ToList();
} }
public override Task<List<object>?> GetDownloadsToChangeCategoryAsync(List<string> categories) public override List<object>? FilterDownloadsToChangeCategoryAsync(List<object>? downloads, List<string> categories)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
/// <inheritdoc/> /// <inheritdoc/>
public override async Task CleanDownloadsAsync(List<object> downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes) public override async Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean,
HashSet<string> excludedHashes)
{ {
foreach (TorrentStatus download in downloads) foreach (TorrentStatus download in downloads)
{ {
@@ -252,7 +253,7 @@ public class DelugeService : DownloadService, IDelugeService
continue; continue;
} }
await _dryRunInterceptor.InterceptAsync(DeleteDownloadAsync, download.Hash); await _dryRunInterceptor.InterceptAsync(DeleteDownload, download.Hash);
_logger.LogInformation( _logger.LogInformation(
"download cleaned | {reason} reached | {name}", "download cleaned | {reason} reached | {name}",
@@ -271,14 +272,14 @@ public class DelugeService : DownloadService, IDelugeService
throw new NotImplementedException(); 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(); throw new NotImplementedException();
} }
/// <inheritdoc/> /// <inheritdoc/>
[DryRunSafeguard] [DryRunSafeguard]
public override async Task DeleteDownloadAsync(string hash) public override async Task DeleteDownload(string hash)
{ {
hash = hash.ToLowerInvariant(); hash = hash.ToLowerInvariant();
@@ -1,5 +1,4 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Common.Configuration.ContentBlocker; using Common.Configuration.ContentBlocker;
using Common.Configuration.DownloadCleaner; using Common.Configuration.DownloadCleaner;
@@ -76,19 +75,22 @@ public abstract class DownloadService : IDownloadService
); );
/// <inheritdoc/> /// <inheritdoc/>
public abstract Task DeleteDownloadAsync(string hash); public abstract Task DeleteDownload(string hash);
/// <inheritdoc/> /// <inheritdoc/>
public abstract Task<List<object>?> GetDownloadsToBeCleanedAsync(List<CleanCategory> categories); public abstract Task<List<object>?> GetSeedingDownloads();
/// <inheritdoc/>
public abstract List<object>? FilterDownloadsToBeCleanedAsync(List<object>? downloads, List<CleanCategory> categories);
/// <inheritdoc/> /// <inheritdoc/>
public abstract Task<List<object>?> GetDownloadsToChangeCategoryAsync(List<string> categories); public abstract List<object>? FilterDownloadsToChangeCategoryAsync(List<object>? downloads, List<string> categories);
/// <inheritdoc/> /// <inheritdoc/>
public abstract Task CleanDownloadsAsync(List<object> downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes); public abstract Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes);
/// <inheritdoc/> /// <inheritdoc/>
public abstract Task ChangeCategoryForNoHardLinksAsync(List<object> downloads, HashSet<string> excludedHashes); public abstract Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes);
/// <inheritdoc/> /// <inheritdoc/>
public abstract Task CreateCategoryAsync(string name); public abstract Task CreateCategoryAsync(string name);
@@ -53,22 +53,22 @@ public class DummyDownloadService : DownloadService
throw new NotImplementedException(); throw new NotImplementedException();
} }
public override Task<List<object>?> GetDownloadsToBeCleanedAsync(List<CleanCategory> categories) public override List<object>? FilterDownloadsToBeCleanedAsync(List<object>? downloads, List<CleanCategory> categories)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public override Task<List<object>?> GetDownloadsToChangeCategoryAsync(List<string> categories) public override List<object>? FilterDownloadsToChangeCategoryAsync(List<object>? downloads, List<string> categories)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public override Task CleanDownloadsAsync(List<object> downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes) public override Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes)
{ {
throw new NotImplementedException(); 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(); throw new NotImplementedException();
} }
@@ -78,7 +78,7 @@ public class DummyDownloadService : DownloadService
throw new NotImplementedException(); throw new NotImplementedException();
} }
public override Task DeleteDownloadAsync(string hash) public override Task DeleteDownload(string hash)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -32,38 +32,46 @@ public interface IDownloadService : IDisposable
); );
/// <summary> /// <summary>
/// Fetches all downloads that should be cleaned. /// Fetches all seeding downloads.
/// </summary> /// </summary>
/// <param name="categories">The categories by which to filter the downloads.</param> /// <returns>A list of downloads that are seeding.</returns>
/// <returns>A list of downloads for the provided categories.</returns> Task<List<object>?> GetSeedingDownloads();
Task<List<object>?> GetDownloadsToBeCleanedAsync(List<CleanCategory> categories);
/// <summary> /// <summary>
/// Fetches all downloads that should have their category changed. /// Filters downloads that should be cleaned.
/// </summary> /// </summary>
/// <param name="downloads">The downloads to filter.</param>
/// <param name="categories">The categories by which to filter the downloads.</param> /// <param name="categories">The categories by which to filter the downloads.</param>
/// <returns>A list of downloads for the provided categories.</returns> /// <returns>A list of downloads for the provided categories.</returns>
Task<List<object>?> GetDownloadsToChangeCategoryAsync(List<string> categories); List<object>? FilterDownloadsToBeCleanedAsync(List<object>? downloads, List<CleanCategory> categories);
/// <summary>
/// Filters downloads that should have their category changed.
/// </summary>
/// <param name="downloads">The downloads to filter.</param>
/// <param name="categories">The categories by which to filter the downloads.</param>
/// <returns>A list of downloads for the provided categories.</returns>
List<object>? FilterDownloadsToChangeCategoryAsync(List<object>? downloads, List<string> categories);
/// <summary> /// <summary>
/// Cleans the downloads. /// Cleans the downloads.
/// </summary> /// </summary>
/// <param name="downloads">The downloads to clean.</param> /// <param name="downloads">The downloads to clean.</param>
/// <param name="categoriesToClean">The categories that should be cleaned.</param> /// <param name="categoriesToClean">The categories that should be cleaned.</param>
/// <param name="excludedHashes">The hashes that should not be cleaned.</param> /// <param name="excludedHashes">The hashes that should not be cleaned.</param>
Task CleanDownloadsAsync(List<object> downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes); Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes);
/// <summary> /// <summary>
/// Changes the category for downloads that have no hardlinks. /// Changes the category for downloads that have no hardlinks.
/// </summary> /// </summary>
/// <param name="downloads">The downloads to change.</param> /// <param name="downloads">The downloads to change.</param>
/// <param name="excludedHashes"></param> /// <param name="excludedHashes"></param>
Task ChangeCategoryForNoHardLinksAsync(List<object> downloads, HashSet<string> excludedHashes); Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes);
/// <summary> /// <summary>
/// Deletes a download item. /// Deletes a download item.
/// </summary> /// </summary>
public Task DeleteDownloadAsync(string hash); public Task DeleteDownload(string hash);
/// <summary> /// <summary>
/// Creates a category. /// Creates a category.
@@ -209,31 +209,42 @@ public class QBitService : DownloadService, IQBitService
} }
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<List<object>?> GetDownloadsToBeCleanedAsync(List<CleanCategory> categories) => public override async Task<List<object>?> GetSeedingDownloads() =>
(await _client.GetTorrentListAsync(new() (await _client.GetTorrentListAsync(new()
{ {
Filter = TorrentListFilter.Seeding Filter = TorrentListFilter.Seeding
})) }))
?.Where(x => !string.IsNullOrEmpty(x.Hash)) ?.Where(x => !string.IsNullOrEmpty(x.Hash))
.Where(x => categories.Any(cat => cat.Name.Equals(x.Category, StringComparison.InvariantCultureIgnoreCase)))
.Cast<object>() .Cast<object>()
.ToList(); .ToList();
public override async Task<List<object>?> GetDownloadsToChangeCategoryAsync(List<string> categories) /// <inheritdoc/>
{ public override List<object>? FilterDownloadsToBeCleanedAsync(List<object>? downloads, List<CleanCategory> categories) =>
return (await _client.GetTorrentListAsync(new() downloads
{ ?.Cast<TorrentInfo>()
Filter = TorrentListFilter.Seeding .Where(x => !string.IsNullOrEmpty(x.Hash))
})) .Where(x => categories.Any(cat => cat.Name.Equals(x.Category, StringComparison.InvariantCultureIgnoreCase)))
?.Where(x => !string.IsNullOrEmpty(x.Hash)) .Cast<object>()
.ToList();
/// <inheritdoc/>
public override List<object>? FilterDownloadsToChangeCategoryAsync(List<object>? downloads, List<string> categories) =>
downloads
?.Cast<TorrentInfo>()
.Where(x => !string.IsNullOrEmpty(x.Hash))
.Where(x => categories.Any(cat => cat.Equals(x.Category, StringComparison.InvariantCultureIgnoreCase))) .Where(x => categories.Any(cat => cat.Equals(x.Category, StringComparison.InvariantCultureIgnoreCase)))
.Cast<object>() .Cast<object>()
.ToList(); .ToList();
}
/// <inheritdoc/> /// <inheritdoc/>
public override async Task CleanDownloadsAsync(List<object> downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes) public override async Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean,
HashSet<string> excludedHashes)
{ {
if (downloads?.Count is null or 0)
{
return;
}
foreach (TorrentInfo download in downloads) foreach (TorrentInfo download in downloads)
{ {
if (string.IsNullOrEmpty(download.Hash)) if (string.IsNullOrEmpty(download.Hash))
@@ -286,7 +297,7 @@ public class QBitService : DownloadService, IQBitService
continue; continue;
} }
await _dryRunInterceptor.InterceptAsync(DeleteDownloadAsync, download.Hash); await _dryRunInterceptor.InterceptAsync(DeleteDownload, download.Hash);
_logger.LogInformation( _logger.LogInformation(
"download cleaned | {reason} reached | {name}", "download cleaned | {reason} reached | {name}",
@@ -312,8 +323,13 @@ public class QBitService : DownloadService, IQBitService
await _client.AddCategoryAsync(name); await _client.AddCategoryAsync(name);
} }
public override async Task ChangeCategoryForNoHardLinksAsync(List<object> downloads, HashSet<string> excludedHashes) public override async Task ChangeCategoryForNoHardLinksAsync(List<object>? downloads, HashSet<string> excludedHashes)
{ {
if (downloads?.Count is null or 0)
{
return;
}
if (_downloadCleanerConfig.NoHardLinksIgnoreRootDir) if (_downloadCleanerConfig.NoHardLinksIgnoreRootDir)
{ {
downloads downloads
@@ -405,6 +421,7 @@ public class QBitService : DownloadService, IQBitService
} }
await _dryRunInterceptor.InterceptAsync(ChangeCategory, download.Hash, _downloadCleanerConfig.NoHardLinksCategory); await _dryRunInterceptor.InterceptAsync(ChangeCategory, download.Hash, _downloadCleanerConfig.NoHardLinksCategory);
download.Category = _downloadCleanerConfig.NoHardLinksCategory;
_logger.LogInformation("category changed for {name}", download.Name); _logger.LogInformation("category changed for {name}", download.Name);
@@ -414,7 +431,7 @@ public class QBitService : DownloadService, IQBitService
/// <inheritdoc/> /// <inheritdoc/>
[DryRunSafeguard] [DryRunSafeguard]
public override async Task DeleteDownloadAsync(string hash) public override async Task DeleteDownload(string hash)
{ {
await _client.DeleteAsync(hash, deleteDownloadedData: true); await _client.DeleteAsync(hash, deleteDownloadedData: true);
} }
@@ -183,7 +183,7 @@ public class TransmissionService : DownloadService, ITransmissionService
} }
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<List<object>?> GetDownloadsToBeCleanedAsync(List<CleanCategory> categories) public override async List<object>? FilterDownloadsToBeCleanedAsync(List<object>? downloads, List<CleanCategory> categories)
{ {
string[] fields = [ string[] fields = [
TorrentFields.FILES, TorrentFields.FILES,
@@ -220,13 +220,14 @@ public class TransmissionService : DownloadService, ITransmissionService
.ToList(); .ToList();
} }
public override Task<List<object>?> GetDownloadsToChangeCategoryAsync(List<string> categories) public override List<object>? FilterDownloadsToChangeCategoryAsync(List<object>? downloads, List<string> categories)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
/// <inheritdoc/> /// <inheritdoc/>
public override async Task CleanDownloadsAsync(List<object> downloads, List<CleanCategory> categoriesToClean, HashSet<string> excludedHashes) public override async Task CleanDownloadsAsync(List<object>? downloads, List<CleanCategory> categoriesToClean,
HashSet<string> excludedHashes)
{ {
foreach (TorrentInfo download in downloads) foreach (TorrentInfo download in downloads)
{ {
@@ -294,12 +295,12 @@ public class TransmissionService : DownloadService, ITransmissionService
throw new NotImplementedException(); 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(); throw new NotImplementedException();
} }
public override async Task DeleteDownloadAsync(string hash) public override async Task DeleteDownload(string hash)
{ {
TorrentInfo? torrent = await GetTorrentAsync(hash); TorrentInfo? torrent = await GetTorrentAsync(hash);