add content blocker (#5)

* refactored code
added deluge support
added transmission support
added content blocker
added blacklist and whitelist

* increased level on some logs; updated test docker compose; updated dev appsettings

* updated docker compose and readme

* moved some logs

* fixed env var typo; fixed sonarr and radarr default download client
This commit is contained in:
Marius Nechifor
2024-11-18 20:08:01 +02:00
committed by GitHub
parent b323cb40ae
commit e0a6c7842b
154 changed files with 4752 additions and 789 deletions
-64
View File
@@ -1,64 +0,0 @@
using Common.Configuration;
using Executable.Jobs;
using Infrastructure.Verticals.Arr;
using Infrastructure.Verticals.QueueCleaner;
namespace Executable;
using Quartz;
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) =>
services
.AddLogging(builder => builder.ClearProviders().AddConsole())
.AddHttpClient()
.AddConfiguration(configuration)
.AddServices()
.AddQuartzServices(configuration);
private static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration configuration) =>
services
.Configure<QBitConfig>(configuration.GetSection(QBitConfig.SectionName))
.Configure<SonarrConfig>(configuration.GetSection(SonarrConfig.SectionName))
.Configure<RadarrConfig>(configuration.GetSection(RadarrConfig.SectionName));
private static IServiceCollection AddServices(this IServiceCollection services) =>
services
.AddTransient<SonarrClient>()
.AddTransient<RadarrClient>()
.AddTransient<QueueCleanerJob>()
.AddTransient<QueueCleanerHandler>();
private static IServiceCollection AddQuartzServices(this IServiceCollection services, IConfiguration configuration) =>
services
.AddQuartz(q =>
{
TriggersConfig? config = configuration.GetRequiredSection(TriggersConfig.SectionName).Get<TriggersConfig>();
if (config is null)
{
throw new NullReferenceException("Quartz configuration is null");
}
q.AddQueueCleanerJob(config.QueueCleaner);
})
.AddQuartzHostedService(opt =>
{
opt.WaitForJobsToComplete = true;
});
private static void AddQueueCleanerJob(this IServiceCollectionQuartzConfigurator q, string trigger)
{
q.AddJob<QueueCleanerJob>(opts =>
{
opts.WithIdentity(nameof(QueueCleanerJob));
});
q.AddTrigger(opts =>
{
opts.ForJob(nameof(QueueCleanerJob))
.WithIdentity($"{nameof(QueueCleanerJob)}-trigger")
.WithCronSchedule(trigger);
});
}
}
@@ -0,0 +1,16 @@
using Common.Configuration;
using Common.Configuration.ContentBlocker;
namespace Executable.DependencyInjection;
public static class ConfigurationDI
{
public static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration configuration) =>
services
.Configure<ContentBlockerConfig>(configuration.GetSection(ContentBlockerConfig.SectionName))
.Configure<QBitConfig>(configuration.GetSection(QBitConfig.SectionName))
.Configure<DelugeConfig>(configuration.GetSection(DelugeConfig.SectionName))
.Configure<TransmissionConfig>(configuration.GetSection(TransmissionConfig.SectionName))
.Configure<SonarrConfig>(configuration.GetSection(SonarrConfig.SectionName))
.Configure<RadarrConfig>(configuration.GetSection(RadarrConfig.SectionName));
}
@@ -0,0 +1,50 @@
using System.Net;
using Common.Configuration;
using Common.Configuration.ContentBlocker;
using Executable.Jobs;
using Infrastructure.Verticals.Arr;
using Infrastructure.Verticals.ContentBlocker;
using Infrastructure.Verticals.DownloadClient;
using Infrastructure.Verticals.DownloadClient.Deluge;
using Infrastructure.Verticals.DownloadClient.QBittorrent;
using Infrastructure.Verticals.DownloadClient.Transmission;
using Infrastructure.Verticals.QueueCleaner;
namespace Executable.DependencyInjection;
public static class MainDI
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) =>
services
.AddLogging(builder => builder.ClearProviders().AddConsole())
.AddHttpClients()
.AddConfiguration(configuration)
.AddServices()
.AddQuartzServices(configuration);
private static IServiceCollection AddHttpClients(this IServiceCollection services)
{
// add default HttpClient
services.AddHttpClient();
// add Deluge HttpClient
services
.AddHttpClient(nameof(DelugeService), x =>
{
x.Timeout = TimeSpan.FromSeconds(5);
})
.ConfigurePrimaryHttpMessageHandler(_ =>
{
return new HttpClientHandler
{
AllowAutoRedirect = true,
UseCookies = true,
CookieContainer = new CookieContainer(),
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
};
});
return services;
}
}
@@ -0,0 +1,99 @@
using Common.Configuration;
using Common.Configuration.ContentBlocker;
using Common.Configuration.QueueCleaner;
using Executable.Jobs;
using Infrastructure.Verticals.ContentBlocker;
using Infrastructure.Verticals.QueueCleaner;
using Quartz;
namespace Executable.DependencyInjection;
public static class QuartzDI
{
public static IServiceCollection AddQuartzServices(this IServiceCollection services, IConfiguration configuration) =>
services
.AddQuartz(q =>
{
TriggersConfig? config = configuration
.GetRequiredSection(TriggersConfig.SectionName)
.Get<TriggersConfig>();
if (config is null)
{
throw new NullReferenceException("triggers configuration is null");
}
q.AddQueueCleanerJob(configuration, config.QueueCleaner);
q.AddContentBlockerJob(configuration, config.ContentBlocker);
})
.AddQuartzHostedService(opt =>
{
opt.WaitForJobsToComplete = true;
});
private static void AddQueueCleanerJob(
this IServiceCollectionQuartzConfigurator q,
IConfiguration configuration,
string trigger
)
{
QueueCleanerConfig? config = configuration
.GetRequiredSection(QueueCleanerConfig.SectionName)
.Get<QueueCleanerConfig>();
if (config is null)
{
throw new NullReferenceException($"{nameof(QueueCleaner)} configuration is null");
}
if (!config.Enabled)
{
return;
}
q.AddJob<QueueCleanerJob>(opts =>
{
opts.WithIdentity(nameof(QueueCleanerJob));
});
q.AddTrigger(opts =>
{
opts.ForJob(nameof(QueueCleanerJob))
.WithIdentity($"{nameof(QueueCleanerJob)}-trigger")
.WithCronSchedule(trigger, x =>x.WithMisfireHandlingInstructionDoNothing());
});
}
private static void AddContentBlockerJob(
this IServiceCollectionQuartzConfigurator q,
IConfiguration configuration,
string trigger
)
{
ContentBlockerConfig? config = configuration
.GetRequiredSection(ContentBlockerConfig.SectionName)
.Get<ContentBlockerConfig>();
if (config is null)
{
throw new NullReferenceException($"{nameof(ContentBlocker)} configuration is null");
}
if (!config.Enabled)
{
return;
}
q.AddJob<ContentBlockerJob>(opts =>
{
opts.WithIdentity(nameof(ContentBlockerJob));
});
q.AddTrigger(opts =>
{
opts.ForJob(nameof(ContentBlockerJob))
.WithIdentity($"{nameof(ContentBlockerJob)}-trigger")
.WithCronSchedule(trigger, x =>x.WithMisfireHandlingInstructionDoNothing());
});
}
}
@@ -0,0 +1,29 @@
using Executable.Jobs;
using Infrastructure.Verticals.Arr;
using Infrastructure.Verticals.ContentBlocker;
using Infrastructure.Verticals.DownloadClient;
using Infrastructure.Verticals.DownloadClient.Deluge;
using Infrastructure.Verticals.DownloadClient.QBittorrent;
using Infrastructure.Verticals.DownloadClient.Transmission;
using Infrastructure.Verticals.QueueCleaner;
namespace Executable.DependencyInjection;
public static class ServicesDI
{
public static IServiceCollection AddServices(this IServiceCollection services) =>
services
.AddTransient<SonarrClient>()
.AddTransient<RadarrClient>()
.AddTransient<QueueCleanerJob>()
.AddTransient<ContentBlockerJob>()
.AddTransient<QueueCleaner>()
.AddTransient<ContentBlocker>()
.AddTransient<FilenameEvaluator>()
.AddTransient<QBitService>()
.AddTransient<DelugeService>()
.AddTransient<TransmissionService>()
.AddTransient<ArrQueueIterator>()
.AddTransient<DownloadServiceFactory>()
.AddSingleton<BlocklistProvider>();
}
+32
View File
@@ -0,0 +1,32 @@
using Infrastructure.Verticals.ContentBlocker;
using Quartz;
namespace Executable.Jobs;
[DisallowConcurrentExecution]
public sealed class ContentBlockerJob : IJob
{
private readonly ILogger<QueueCleanerJob> _logger;
private readonly ContentBlocker _contentBlocker;
public ContentBlockerJob(
ILogger<QueueCleanerJob> logger,
ContentBlocker contentBlocker
)
{
_logger = logger;
_contentBlocker = contentBlocker;
}
public async Task Execute(IJobExecutionContext context)
{
try
{
await _contentBlocker.ExecuteAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, $"{nameof(ContentBlockerJob)} failed");
}
}
}
+8 -5
View File
@@ -6,20 +6,23 @@ namespace Executable.Jobs;
[DisallowConcurrentExecution]
public sealed class QueueCleanerJob : IJob
{
private ILogger<QueueCleanerJob> _logger;
private QueueCleanerHandler _handler;
private readonly ILogger<QueueCleanerJob> _logger;
private readonly QueueCleaner _queueCleaner;
public QueueCleanerJob(ILogger<QueueCleanerJob> logger, QueueCleanerHandler handler)
public QueueCleanerJob(
ILogger<QueueCleanerJob> logger,
QueueCleaner queueCleaner
)
{
_logger = logger;
_handler = handler;
_queueCleaner = queueCleaner;
}
public async Task Execute(IJobExecutionContext context)
{
try
{
await _handler.HandleAsync();
await _queueCleaner.ExecuteAsync();
}
catch (Exception ex)
{
+1 -1
View File
@@ -1,4 +1,4 @@
using Executable;
using Executable.DependencyInjection;
var builder = Host.CreateApplicationBuilder(args);
+55 -3
View File
@@ -1,11 +1,63 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
"Default": "Debug",
"Microsoft.Hosting.Lifetime": "Information",
"Quartz": "Warning",
"System.Net.Http.HttpClient": "Error"
}
},
"Triggers": {
"QueueCleaner": "0 0/1 * * * ?"
"QueueCleaner": "0/10 * * * * ?",
"ContentBlocker": "0/10 * * * * ?"
},
"ContentBlocker": {
"Enabled": false,
"Blacklist": {
"Enabled": false,
"Path": "https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist"
},
"Whitelist": {
"Enabled": false,
"Path": "https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/whitelist"
}
},
"QueueCleaner": {
"Enabled": true
},
"qBittorrent": {
"Enabled": true,
"Url": "http://localhost:8080",
"Username": "test",
"Password": "testing"
},
"Deluge": {
"Enabled": false,
"Url": "http://localhost:8112",
"Password": "testing"
},
"Transmission": {
"Enabled": false,
"Url": "http://localhost:9091",
"Username": "test",
"Password": "testing"
},
"Sonarr": {
"Enabled": true,
"Instances": [
{
"Url": "http://localhost:8989",
"ApiKey": "96736c3eb3144936b8f1d62d27be8cee"
}
]
},
"Radarr": {
"Enabled": true,
"Instances": [
{
"Url": "http://localhost:7878",
"ApiKey": "705b553732ab4167ab23909305d60600"
}
]
}
}
+28 -1
View File
@@ -8,13 +8,40 @@
}
},
"Triggers": {
"QueueCleaner": "0 0/5 * * * ?"
"QueueCleaner": "0 0/5 * * * ?",
"ContentBlocker": "0 0/5 * * * ?"
},
"ContentBlocker": {
"Enabled": false,
"Blacklist": {
"Enabled": false,
"Path": ""
},
"Whitelist": {
"Enabled": false,
"Path": ""
}
},
"QueueCleaner": {
"Enabled": true
},
"qBittorrent": {
"Enabled": true,
"Url": "http://localhost:8080",
"Username": "",
"Password": ""
},
"Deluge": {
"Enabled": false,
"Url": "http://localhost:8112",
"Password": "testing"
},
"Transmission": {
"Enabled": false,
"Url": "http://localhost:9091",
"Username": "test",
"Password": "testing"
},
"Sonarr": {
"Enabled": true,
"Instances": [