Add Lidarr support (#30)

This commit is contained in:
Marius Nechifor
2025-01-15 23:55:34 +02:00
committed by GitHub
parent 2bc8e445ce
commit 922f586706
63 changed files with 943 additions and 243 deletions
@@ -78,6 +78,11 @@ public sealed class DelugeClient
await SendRequest<DelugeResponse<object>>("core.set_torrent_options", hash, filePriorities);
}
public async Task<DelugeResponse<object>> DeleteTorrent(string hash)
{
return await SendRequest<DelugeResponse<object>>("core.remove_torrents", new List<string> { hash }, true);
}
private async Task<String> PostJson(String json)
{
StringContent content = new StringContent(json);
@@ -1,3 +1,6 @@
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Common.Configuration.ContentBlocker;
using Common.Configuration.DownloadClient;
using Common.Configuration.QueueCleaner;
using Domain.Models.Deluge.Response;
@@ -30,6 +33,7 @@ public sealed class DelugeService : DownloadServiceBase
await _client.LoginAsync();
}
/// <inheritdoc/>
public override async Task<RemoveResult> ShouldRemoveFromArrQueueAsync(string hash)
{
hash = hash.ToLowerInvariant();
@@ -70,7 +74,13 @@ public sealed class DelugeService : DownloadServiceBase
return result;
}
public override async Task BlockUnwantedFilesAsync(string hash)
/// <inheritdoc/>
public override async Task<bool> BlockUnwantedFilesAsync(
string hash,
BlocklistType blocklistType,
ConcurrentBag<string> patterns,
ConcurrentBag<Regex> regexes
)
{
hash = hash.ToLowerInvariant();
@@ -79,14 +89,14 @@ public sealed class DelugeService : DownloadServiceBase
if (status?.Hash is null)
{
_logger.LogDebug("failed to find torrent {hash} in the download client", hash);
return;
return false;
}
if (_queueCleanerConfig.StalledIgnorePrivate && status.Private)
{
// ignore private trackers
_logger.LogDebug("skip files check | download is private | {name}", status.Name);
return;
return false;
}
DelugeContents? contents = null;
@@ -102,18 +112,27 @@ public sealed class DelugeService : DownloadServiceBase
if (contents is null)
{
return;
return false;
}
Dictionary<int, int> priorities = [];
bool hasPriorityUpdates = false;
long totalFiles = 0;
long totalUnwantedFiles = 0;
ProcessFiles(contents.Contents, (name, file) =>
{
totalFiles++;
int priority = file.Priority;
if (file.Priority is not 0 && !_filenameEvaluator.IsValid(name))
if (file.Priority is 0)
{
totalUnwantedFiles++;
}
if (file.Priority is not 0 && !_filenameEvaluator.IsValid(name, blocklistType, patterns, regexes))
{
totalUnwantedFiles++;
priority = 0;
hasPriorityUpdates = true;
_logger.LogInformation("unwanted file found | {file}", file.Path);
@@ -124,7 +143,7 @@ public sealed class DelugeService : DownloadServiceBase
if (!hasPriorityUpdates)
{
return;
return false;
}
_logger.LogDebug("changing priorities | torrent {hash}", hash);
@@ -134,7 +153,23 @@ public sealed class DelugeService : DownloadServiceBase
.Select(x => x.Value)
.ToList();
if (totalUnwantedFiles == totalFiles)
{
// Skip marking files as unwanted. The download will be removed completely.
return true;
}
await _client.ChangeFilesPriority(hash, sortedPriorities);
return false;
}
/// <inheritdoc/>
public override async Task Delete(string hash)
{
hash = hash.ToLowerInvariant();
await _client.DeleteTorrent(hash);
}
private bool IsItemStuckAndShouldRemove(TorrentStatus status)
@@ -173,8 +208,13 @@ public sealed class DelugeService : DownloadServiceBase
);
}
private static void ProcessFiles(Dictionary<string, DelugeFileOrDirectory> contents, Action<string, DelugeFileOrDirectory> processFile)
private static void ProcessFiles(Dictionary<string, DelugeFileOrDirectory>? contents, Action<string, DelugeFileOrDirectory> processFile)
{
if (contents is null)
{
return;
}
foreach (var (name, data) in contents)
{
switch (data.Type)
@@ -1,4 +1,7 @@
using Common.Configuration.QueueCleaner;
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Common.Configuration.ContentBlocker;
using Common.Configuration.QueueCleaner;
using Domain.Enums;
using Infrastructure.Verticals.ContentBlocker;
using Infrastructure.Verticals.ItemStriker;
@@ -33,8 +36,23 @@ public abstract class DownloadServiceBase : IDownloadService
public abstract Task<RemoveResult> ShouldRemoveFromArrQueueAsync(string hash);
public abstract Task BlockUnwantedFilesAsync(string hash);
/// <inheritdoc/>
public abstract Task<bool> BlockUnwantedFilesAsync(
string hash,
BlocklistType blocklistType,
ConcurrentBag<string> patterns,
ConcurrentBag<Regex> regexes
);
/// <inheritdoc/>
public abstract Task Delete(string hash);
/// <summary>
/// Strikes an item and checks if the limit has been reached.
/// </summary>
/// <param name="hash">The torrent hash.</param>
/// <param name="itemName">The name or title of the item.</param>
/// <returns>True if the limit has been reached; otherwise, false.</returns>
protected bool StrikeAndCheckLimit(string hash, string itemName)
{
return _striker.StrikeAndCheckLimit(hash, itemName, _queueCleanerConfig.StalledMaxStrikes, StrikeType.Stalled);
@@ -1,4 +1,7 @@
using Common.Configuration.QueueCleaner;
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Common.Configuration.ContentBlocker;
using Common.Configuration.QueueCleaner;
using Infrastructure.Verticals.ContentBlocker;
using Infrastructure.Verticals.ItemStriker;
using Microsoft.Extensions.Logging;
@@ -26,7 +29,12 @@ public sealed class DummyDownloadService : DownloadServiceBase
throw new NotImplementedException();
}
public override Task BlockUnwantedFilesAsync(string hash)
public override Task<bool> BlockUnwantedFilesAsync(string hash, BlocklistType blocklistType, ConcurrentBag<string> patterns, ConcurrentBag<Regex> regexes)
{
throw new NotImplementedException();
}
public override Task Delete(string hash)
{
throw new NotImplementedException();
}
@@ -1,10 +1,36 @@
namespace Infrastructure.Verticals.DownloadClient;
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Common.Configuration.ContentBlocker;
namespace Infrastructure.Verticals.DownloadClient;
public interface IDownloadService : IDisposable
{
public Task LoginAsync();
/// <summary>
/// Checks whether the download should be removed from the *arr queue.
/// </summary>
/// <param name="hash">The download hash.</param>
public Task<RemoveResult> ShouldRemoveFromArrQueueAsync(string hash);
public Task BlockUnwantedFilesAsync(string hash);
/// <summary>
/// Blocks unwanted files from being fully downloaded.
/// </summary>
/// <param name="hash">The torrent hash.</param>
/// <param name="blocklistType">The <see cref="BlocklistType"/>.</param>
/// <param name="patterns">The patterns to test the files against.</param>
/// <param name="regexes">The regexes to test the files against.</param>
/// <returns>True if all files have been blocked; otherwise false.</returns>
public Task<bool> BlockUnwantedFilesAsync(
string hash,
BlocklistType blocklistType,
ConcurrentBag<string> patterns,
ConcurrentBag<Regex> regexes
);
/// <summary>
/// Deletes a download item.
/// </summary>
public Task Delete(string hash);
}
@@ -1,3 +1,6 @@
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Common.Configuration.ContentBlocker;
using Common.Configuration.DownloadClient;
using Common.Configuration.QueueCleaner;
using Common.Helpers;
@@ -38,6 +41,7 @@ public sealed class QBitService : DownloadServiceBase
await _client.LoginAsync(_config.Username, _config.Password);
}
/// <inheritdoc/>
public override async Task<RemoveResult> ShouldRemoveFromArrQueueAsync(string hash)
{
RemoveResult result = new();
@@ -83,7 +87,13 @@ public sealed class QBitService : DownloadServiceBase
return result;
}
public override async Task BlockUnwantedFilesAsync(string hash)
/// <inheritdoc/>
public override async Task<bool> BlockUnwantedFilesAsync(
string hash,
BlocklistType blocklistType,
ConcurrentBag<string> patterns,
ConcurrentBag<Regex> regexes
)
{
TorrentInfo? torrent = (await _client.GetTorrentListAsync(new TorrentListQuery { Hashes = [hash] }))
.FirstOrDefault();
@@ -91,7 +101,7 @@ public sealed class QBitService : DownloadServiceBase
if (torrent is null)
{
_logger.LogDebug("failed to find torrent {hash} in the download client", hash);
return;
return false;
}
TorrentProperties? torrentProperties = await _client.GetTorrentPropertiesAsync(hash);
@@ -99,7 +109,7 @@ public sealed class QBitService : DownloadServiceBase
if (torrentProperties is null)
{
_logger.LogDebug("failed to find torrent properties {hash} in the download client", hash);
return;
return false;
}
bool isPrivate = torrentProperties.AdditionalData.TryGetValue("is_private", out var dictValue) &&
@@ -110,15 +120,19 @@ public sealed class QBitService : DownloadServiceBase
{
// ignore private trackers
_logger.LogDebug("skip files check | download is private | {name}", torrent.Name);
return;
return false;
}
IReadOnlyList<TorrentContent>? files = await _client.GetTorrentContentsAsync(hash);
if (files is null)
{
return;
return false;
}
List<int> unwantedFiles = [];
long totalFiles = 0;
long totalUnwantedFiles = 0;
foreach (TorrentContent file in files)
{
@@ -127,14 +141,47 @@ public sealed class QBitService : DownloadServiceBase
continue;
}
if (file.Priority is TorrentContentPriority.Skip || _filenameEvaluator.IsValid(file.Name))
totalFiles++;
if (file.Priority is TorrentContentPriority.Skip)
{
totalUnwantedFiles++;
continue;
}
if (_filenameEvaluator.IsValid(file.Name, blocklistType, patterns, regexes))
{
continue;
}
_logger.LogInformation("unwanted file found | {file}", file.Name);
await _client.SetFilePriorityAsync(hash, file.Index.Value, TorrentContentPriority.Skip);
unwantedFiles.Add(file.Index.Value);
totalUnwantedFiles++;
}
if (unwantedFiles.Count is 0)
{
return false;
}
if (totalUnwantedFiles == totalFiles)
{
// Skip marking files as unwanted. The download will be removed completely.
return true;
}
foreach (int fileIndex in unwantedFiles)
{
await _client.SetFilePriorityAsync(hash, fileIndex, TorrentContentPriority.Skip);
}
return false;
}
/// <inheritdoc/>
public override async Task Delete(string hash)
{
await _client.DeleteAsync(hash, deleteDownloadedData: true);
}
public override void Dispose()
@@ -1,4 +1,7 @@
using Common.Configuration.DownloadClient;
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Common.Configuration.ContentBlocker;
using Common.Configuration.DownloadClient;
using Common.Configuration.QueueCleaner;
using Common.Helpers;
using Infrastructure.Verticals.ContentBlocker;
@@ -41,6 +44,7 @@ public sealed class TransmissionService : DownloadServiceBase
await _client.GetSessionInformationAsync();
}
/// <inheritdoc/>
public override async Task<RemoveResult> ShouldRemoveFromArrQueueAsync(string hash)
{
RemoveResult result = new();
@@ -76,23 +80,31 @@ public sealed class TransmissionService : DownloadServiceBase
return result;
}
public override async Task BlockUnwantedFilesAsync(string hash)
/// <inheritdoc/>
public override async Task<bool> BlockUnwantedFilesAsync(
string hash,
BlocklistType blocklistType,
ConcurrentBag<string> patterns,
ConcurrentBag<Regex> regexes
)
{
TorrentInfo? torrent = await GetTorrentAsync(hash);
if (torrent?.FileStats is null || torrent.Files is null)
{
return;
return false;
}
if (_queueCleanerConfig.StalledIgnorePrivate && (torrent.IsPrivate ?? false))
{
// ignore private trackers
_logger.LogDebug("skip files check | download is private | {name}", torrent.Name);
return;
return false;
}
List<long> unwantedFiles = [];
long totalFiles = 0;
long totalUnwantedFiles = 0;
for (int i = 0; i < torrent.Files.Length; i++)
{
@@ -100,19 +112,34 @@ public sealed class TransmissionService : DownloadServiceBase
{
continue;
}
totalFiles++;
if (!torrent.FileStats[i].Wanted.Value || _filenameEvaluator.IsValid(torrent.Files[i].Name))
if (!torrent.FileStats[i].Wanted.Value)
{
totalUnwantedFiles++;
continue;
}
if (_filenameEvaluator.IsValid(torrent.Files[i].Name, blocklistType, patterns, regexes))
{
continue;
}
_logger.LogInformation("unwanted file found | {file}", torrent.Files[i].Name);
unwantedFiles.Add(i);
totalUnwantedFiles++;
}
if (unwantedFiles.Count is 0)
{
return;
return false;
}
if (totalUnwantedFiles == totalFiles)
{
// Skip marking files as unwanted. The download will be removed completely.
return true;
}
_logger.LogDebug("changing priorities | torrent {hash}", hash);
@@ -122,6 +149,20 @@ public sealed class TransmissionService : DownloadServiceBase
Ids = [ torrent.Id ],
FilesUnwanted = unwantedFiles.ToArray(),
});
return false;
}
public override async Task Delete(string hash)
{
TorrentInfo? torrent = await GetTorrentAsync(hash);
if (torrent is null)
{
return;
}
await _client.TorrentRemoveAsync([torrent.Id], true);
}
public override void Dispose()