Fix interceptor memory leaks (#66)

This commit is contained in:
Flaminel
2025-02-23 17:50:08 +02:00
committed by GitHub
parent 9c8e0ebedc
commit 51bdaf64e4
29 changed files with 174 additions and 196 deletions
+1 -31
View File
@@ -1,8 +1,6 @@
using System.Net; using System.Net;
using Castle.DynamicProxy;
using Common.Configuration.General; using Common.Configuration.General;
using Common.Helpers; using Common.Helpers;
using Infrastructure.Interceptors;
using Infrastructure.Verticals.DownloadClient.Deluge; using Infrastructure.Verticals.DownloadClient.Deluge;
using Infrastructure.Verticals.Notifications.Consumers; using Infrastructure.Verticals.Notifications.Consumers;
using Infrastructure.Verticals.Notifications.Models; using Infrastructure.Verticals.Notifications.Models;
@@ -42,8 +40,7 @@ public static class MainDI
e.PrefetchCount = 1; e.PrefetchCount = 1;
}); });
}); });
}) });
.AddDryRunInterceptor();
private static IServiceCollection AddHttpClients(this IServiceCollection services, IConfiguration configuration) private static IServiceCollection AddHttpClients(this IServiceCollection services, IConfiguration configuration)
{ {
@@ -91,31 +88,4 @@ public static class MainDI
.OrResult(response => !response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.Unauthorized) .OrResult(response => !response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.Unauthorized)
.WaitAndRetryAsync(config.MaxRetries, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))) .WaitAndRetryAsync(config.MaxRetries, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
); );
private static IServiceCollection AddDryRunInterceptor(this IServiceCollection services)
{
services
.Where(s => s.ServiceType != typeof(IDryRunService) && typeof(IDryRunService).IsAssignableFrom(s.ServiceType))
.ToList()
.ForEach(service =>
{
services.Decorate(service.ServiceType, (target, svc) =>
{
ProxyGenerator proxyGenerator = new();
DryRunAsyncInterceptor interceptor = svc.GetRequiredService<DryRunAsyncInterceptor>();
object implementation = proxyGenerator.CreateClassProxyWithTarget(
service.ServiceType,
target,
interceptor
);
((IInterceptedService)target).Proxy = implementation;
return implementation;
});
});
return services;
}
} }
@@ -10,7 +10,7 @@ public static class NotificationsDI
.Configure<NotifiarrConfig>(configuration.GetSection(NotifiarrConfig.SectionName)) .Configure<NotifiarrConfig>(configuration.GetSection(NotifiarrConfig.SectionName))
.AddTransient<INotifiarrProxy, NotifiarrProxy>() .AddTransient<INotifiarrProxy, NotifiarrProxy>()
.AddTransient<INotificationProvider, NotifiarrProvider>() .AddTransient<INotificationProvider, NotifiarrProvider>()
.AddTransient<NotificationPublisher>() .AddTransient<INotificationPublisher, NotificationPublisher>()
.AddTransient<INotificationFactory, NotificationFactory>() .AddTransient<INotificationFactory, NotificationFactory>()
.AddTransient<NotificationService>(); .AddTransient<NotificationService>();
} }
@@ -15,7 +15,7 @@ public static class ServicesDI
{ {
public static IServiceCollection AddServices(this IServiceCollection services) => public static IServiceCollection AddServices(this IServiceCollection services) =>
services services
.AddTransient<DryRunAsyncInterceptor>() .AddTransient<IDryRunInterceptor, DryRunInterceptor>()
.AddTransient<SonarrClient>() .AddTransient<SonarrClient>()
.AddTransient<RadarrClient>() .AddTransient<RadarrClient>()
.AddTransient<LidarrClient>() .AddTransient<LidarrClient>()
@@ -1,6 +1,7 @@
using Common.Configuration.ContentBlocker; using Common.Configuration.ContentBlocker;
using Common.Configuration.DownloadCleaner; using Common.Configuration.DownloadCleaner;
using Common.Configuration.QueueCleaner; using Common.Configuration.QueueCleaner;
using Infrastructure.Interceptors;
using Infrastructure.Verticals.ContentBlocker; using Infrastructure.Verticals.ContentBlocker;
using Infrastructure.Verticals.DownloadClient; using Infrastructure.Verticals.DownloadClient;
using Infrastructure.Verticals.ItemStriker; using Infrastructure.Verticals.ItemStriker;
@@ -53,7 +54,8 @@ public class DownloadServiceFixture : IDisposable
downloadCleanerOptions.Value.Returns(new DownloadCleanerConfig()); downloadCleanerOptions.Value.Returns(new DownloadCleanerConfig());
var filenameEvaluator = Substitute.For<IFilenameEvaluator>(); var filenameEvaluator = Substitute.For<IFilenameEvaluator>();
var notifier = Substitute.For<NotificationPublisher>(); var notifier = Substitute.For<INotificationPublisher>();
var dryRunInterceptor = Substitute.For<IDryRunInterceptor>();
return new TestDownloadService( return new TestDownloadService(
Logger, Logger,
@@ -63,7 +65,8 @@ public class DownloadServiceFixture : IDisposable
Cache, Cache,
filenameEvaluator, filenameEvaluator,
Striker, Striker,
notifier notifier,
dryRunInterceptor
); );
} }
@@ -3,6 +3,7 @@ using System.Text.RegularExpressions;
using Common.Configuration.ContentBlocker; using Common.Configuration.ContentBlocker;
using Common.Configuration.DownloadCleaner; using Common.Configuration.DownloadCleaner;
using Common.Configuration.QueueCleaner; using Common.Configuration.QueueCleaner;
using Infrastructure.Interceptors;
using Infrastructure.Verticals.ContentBlocker; using Infrastructure.Verticals.ContentBlocker;
using Infrastructure.Verticals.DownloadClient; using Infrastructure.Verticals.DownloadClient;
using Infrastructure.Verticals.ItemStriker; using Infrastructure.Verticals.ItemStriker;
@@ -23,9 +24,12 @@ public class TestDownloadService : DownloadService
IMemoryCache cache, IMemoryCache cache,
IFilenameEvaluator filenameEvaluator, IFilenameEvaluator filenameEvaluator,
IStriker striker, IStriker striker,
NotificationPublisher notifier) INotificationPublisher notifier,
: base(logger, queueCleanerConfig, contentBlockerConfig, downloadCleanerConfig, IDryRunInterceptor dryRunInterceptor
cache, filenameEvaluator, striker, notifier) ) : base(
logger, queueCleanerConfig, contentBlockerConfig, downloadCleanerConfig, cache,
filenameEvaluator, striker, notifier, dryRunInterceptor
)
{ {
} }
@@ -12,7 +12,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Castle.Core.AsyncInterceptor" Version="2.1.0" />
<PackageReference Include="FLM.QBittorrent" Version="1.0.0" /> <PackageReference Include="FLM.QBittorrent" Version="1.0.0" />
<PackageReference Include="FLM.Transmission" Version="1.0.2" /> <PackageReference Include="FLM.Transmission" Version="1.0.2" />
<PackageReference Include="Mapster" Version="7.4.0" /> <PackageReference Include="Mapster" Version="7.4.0" />
@@ -1,5 +1,4 @@
using System.Reflection; using System.Reflection;
using Castle.DynamicProxy;
using Common.Attributes; using Common.Attributes;
using Common.Configuration.General; using Common.Configuration.General;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -7,39 +6,68 @@ using Microsoft.Extensions.Options;
namespace Infrastructure.Interceptors; namespace Infrastructure.Interceptors;
public class DryRunAsyncInterceptor : AsyncInterceptorBase public class DryRunInterceptor : IDryRunInterceptor
{ {
private readonly ILogger<DryRunAsyncInterceptor> _logger; private readonly ILogger<DryRunInterceptor> _logger;
private readonly DryRunConfig _config; private readonly DryRunConfig _config;
public DryRunAsyncInterceptor(ILogger<DryRunAsyncInterceptor> logger, IOptions<DryRunConfig> config) public DryRunInterceptor(ILogger<DryRunInterceptor> logger, IOptions<DryRunConfig> config)
{ {
_logger = logger; _logger = logger;
_config = config.Value; _config = config.Value;
} }
protected override async Task InterceptAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task> proceed) public void Intercept(Action action)
{ {
MethodInfo? method = invocation.MethodInvocationTarget ?? invocation.Method; MethodInfo methodInfo = action.Method;
if (IsDryRun(method))
if (IsDryRun(methodInfo))
{ {
_logger.LogInformation("[DRY RUN] skipping method: {name}", method.Name); _logger.LogInformation("[DRY RUN] skipping method: {name}", methodInfo.Name);
return; return;
} }
await proceed(invocation, proceedInfo); action();
} }
protected override async Task<TResult> InterceptAsync<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task<TResult>> proceed) public Task InterceptAsync(Delegate action, params object[] parameters)
{ {
MethodInfo? method = invocation.MethodInvocationTarget ?? invocation.Method; MethodInfo methodInfo = action.Method;
if (IsDryRun(method))
if (IsDryRun(methodInfo))
{ {
_logger.LogInformation("[DRY RUN] skipping method: {name}", method.Name); _logger.LogInformation("[DRY RUN] skipping method: {name}", methodInfo.Name);
return default!; return Task.CompletedTask;
} }
return await proceed(invocation, proceedInfo); object? result = action.DynamicInvoke(parameters);
if (result is Task task)
{
return task;
}
return Task.CompletedTask;
}
public Task<T?> InterceptAsync<T>(Delegate action, params object[] parameters)
{
MethodInfo methodInfo = action.Method;
if (IsDryRun(methodInfo))
{
_logger.LogInformation("[DRY RUN] skipping method: {name}", methodInfo.Name);
return Task.FromResult(default(T));
}
object? result = action.DynamicInvoke(parameters);
if (result is Task<T?> task)
{
return task;
}
return Task.FromResult(default(T));
} }
private bool IsDryRun(MethodInfo method) private bool IsDryRun(MethodInfo method)
@@ -0,0 +1,10 @@
namespace Infrastructure.Interceptors;
public interface IDryRunInterceptor
{
void Intercept(Action action);
Task InterceptAsync(Delegate action, params object[] parameters);
Task<T?> InterceptAsync<T>(Delegate action, params object[] parameters);
}
@@ -1,5 +0,0 @@
namespace Infrastructure.Interceptors;
public interface IDryRunService : IInterceptedService
{
}
@@ -1,6 +0,0 @@
namespace Infrastructure.Interceptors;
public interface IInterceptedService
{
public object Proxy { get; set; }
}
@@ -1,21 +0,0 @@
namespace Infrastructure.Interceptors;
public class InterceptedService : IInterceptedService
{
private object? _proxy;
public object Proxy
{
get
{
if (_proxy is null)
{
throw new Exception("Proxy is not set");
}
return _proxy;
}
set => _proxy = value;
}
}
+7 -10
View File
@@ -15,27 +15,22 @@ using Newtonsoft.Json;
namespace Infrastructure.Verticals.Arr; namespace Infrastructure.Verticals.Arr;
public abstract class ArrClient : InterceptedService, IArrClient, IDryRunService public abstract class ArrClient : IArrClient
{ {
protected readonly ILogger<ArrClient> _logger; protected readonly ILogger<ArrClient> _logger;
protected readonly HttpClient _httpClient; protected readonly HttpClient _httpClient;
protected readonly LoggingConfig _loggingConfig; protected readonly LoggingConfig _loggingConfig;
protected readonly QueueCleanerConfig _queueCleanerConfig; protected readonly QueueCleanerConfig _queueCleanerConfig;
protected readonly IStriker _striker; protected readonly IStriker _striker;
protected readonly IDryRunInterceptor _dryRunInterceptor;
/// <summary>
/// Constructor to be used by interceptors.
/// </summary>
protected ArrClient()
{
}
protected ArrClient( protected ArrClient(
ILogger<ArrClient> logger, ILogger<ArrClient> logger,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
IOptions<LoggingConfig> loggingConfig, IOptions<LoggingConfig> loggingConfig,
IOptions<QueueCleanerConfig> queueCleanerConfig, IOptions<QueueCleanerConfig> queueCleanerConfig,
IStriker striker IStriker striker,
IDryRunInterceptor dryRunInterceptor
) )
{ {
_logger = logger; _logger = logger;
@@ -43,6 +38,7 @@ public abstract class ArrClient : InterceptedService, IArrClient, IDryRunService
_loggingConfig = loggingConfig.Value; _loggingConfig = loggingConfig.Value;
_queueCleanerConfig = queueCleanerConfig.Value; _queueCleanerConfig = queueCleanerConfig.Value;
_striker = striker; _striker = striker;
_dryRunInterceptor = dryRunInterceptor;
} }
public virtual async Task<QueueListResponse> GetQueueItemsAsync(ArrInstance arrInstance, int page) public virtual async Task<QueueListResponse> GetQueueItemsAsync(ArrInstance arrInstance, int page)
@@ -125,7 +121,8 @@ public abstract class ArrClient : InterceptedService, IArrClient, IDryRunService
using HttpRequestMessage request = new(HttpMethod.Delete, uri); using HttpRequestMessage request = new(HttpMethod.Delete, uri);
SetApiKey(request, arrInstance.ApiKey); SetApiKey(request, arrInstance.ApiKey);
using var _ = await ((ArrClient)Proxy).SendRequestAsync(request); HttpResponseMessage? response = await _dryRunInterceptor.InterceptAsync<HttpResponseMessage>(SendRequestAsync, request);
response?.Dispose();
_logger.LogInformation( _logger.LogInformation(
removeFromClient removeFromClient
@@ -5,6 +5,7 @@ using Common.Configuration.QueueCleaner;
using Domain.Models.Arr; using Domain.Models.Arr;
using Domain.Models.Arr.Queue; using Domain.Models.Arr.Queue;
using Domain.Models.Lidarr; using Domain.Models.Lidarr;
using Infrastructure.Interceptors;
using Infrastructure.Verticals.Arr.Interfaces; using Infrastructure.Verticals.Arr.Interfaces;
using Infrastructure.Verticals.ItemStriker; using Infrastructure.Verticals.ItemStriker;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -15,18 +16,14 @@ namespace Infrastructure.Verticals.Arr;
public class LidarrClient : ArrClient, ILidarrClient public class LidarrClient : ArrClient, ILidarrClient
{ {
/// <inheritdoc/>
public LidarrClient()
{
}
public LidarrClient( public LidarrClient(
ILogger<LidarrClient> logger, ILogger<LidarrClient> logger,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
IOptions<LoggingConfig> loggingConfig, IOptions<LoggingConfig> loggingConfig,
IOptions<QueueCleanerConfig> queueCleanerConfig, IOptions<QueueCleanerConfig> queueCleanerConfig,
IStriker striker IStriker striker,
) : base(logger, httpClientFactory, loggingConfig, queueCleanerConfig, striker) IDryRunInterceptor dryRunInterceptor
) : base(logger, httpClientFactory, loggingConfig, queueCleanerConfig, striker, dryRunInterceptor)
{ {
} }
@@ -64,7 +61,8 @@ public class LidarrClient : ArrClient, ILidarrClient
try try
{ {
using var _ = await ((LidarrClient)Proxy).SendRequestAsync(request); HttpResponseMessage? response = await _dryRunInterceptor.InterceptAsync<HttpResponseMessage>(SendRequestAsync, request);
response?.Dispose();
_logger.LogInformation("{log}", GetSearchLog(arrInstance.Url, command, true, logContext)); _logger.LogInformation("{log}", GetSearchLog(arrInstance.Url, command, true, logContext));
} }
@@ -5,6 +5,7 @@ using Common.Configuration.QueueCleaner;
using Domain.Models.Arr; using Domain.Models.Arr;
using Domain.Models.Arr.Queue; using Domain.Models.Arr.Queue;
using Domain.Models.Radarr; using Domain.Models.Radarr;
using Infrastructure.Interceptors;
using Infrastructure.Verticals.Arr.Interfaces; using Infrastructure.Verticals.Arr.Interfaces;
using Infrastructure.Verticals.ItemStriker; using Infrastructure.Verticals.ItemStriker;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -15,18 +16,14 @@ namespace Infrastructure.Verticals.Arr;
public class RadarrClient : ArrClient, IRadarrClient public class RadarrClient : ArrClient, IRadarrClient
{ {
/// <inheritdoc/>
public RadarrClient()
{
}
public RadarrClient( public RadarrClient(
ILogger<ArrClient> logger, ILogger<ArrClient> logger,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
IOptions<LoggingConfig> loggingConfig, IOptions<LoggingConfig> loggingConfig,
IOptions<QueueCleanerConfig> queueCleanerConfig, IOptions<QueueCleanerConfig> queueCleanerConfig,
IStriker striker IStriker striker,
) : base(logger, httpClientFactory, loggingConfig, queueCleanerConfig, striker) IDryRunInterceptor dryRunInterceptor
) : base(logger, httpClientFactory, loggingConfig, queueCleanerConfig, striker, dryRunInterceptor)
{ {
} }
@@ -72,7 +69,8 @@ public class RadarrClient : ArrClient, IRadarrClient
try try
{ {
using var _ = await ((RadarrClient)Proxy).SendRequestAsync(request); HttpResponseMessage? response = await _dryRunInterceptor.InterceptAsync<HttpResponseMessage>(SendRequestAsync, request);
response?.Dispose();
_logger.LogInformation("{log}", GetSearchLog(arrInstance.Url, command, true, logContext)); _logger.LogInformation("{log}", GetSearchLog(arrInstance.Url, command, true, logContext));
} }
@@ -5,6 +5,7 @@ using Common.Configuration.QueueCleaner;
using Domain.Models.Arr; using Domain.Models.Arr;
using Domain.Models.Arr.Queue; using Domain.Models.Arr.Queue;
using Domain.Models.Sonarr; using Domain.Models.Sonarr;
using Infrastructure.Interceptors;
using Infrastructure.Verticals.Arr.Interfaces; using Infrastructure.Verticals.Arr.Interfaces;
using Infrastructure.Verticals.ItemStriker; using Infrastructure.Verticals.ItemStriker;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -16,18 +17,14 @@ namespace Infrastructure.Verticals.Arr;
public class SonarrClient : ArrClient, ISonarrClient public class SonarrClient : ArrClient, ISonarrClient
{ {
/// <inheritdoc/>
public SonarrClient()
{
}
public SonarrClient( public SonarrClient(
ILogger<SonarrClient> logger, ILogger<SonarrClient> logger,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
IOptions<LoggingConfig> loggingConfig, IOptions<LoggingConfig> loggingConfig,
IOptions<QueueCleanerConfig> queueCleanerConfig, IOptions<QueueCleanerConfig> queueCleanerConfig,
IStriker striker IStriker striker,
) : base(logger, httpClientFactory, loggingConfig, queueCleanerConfig, striker) IDryRunInterceptor dryRunInterceptor
) : base(logger, httpClientFactory, loggingConfig, queueCleanerConfig, striker, dryRunInterceptor)
{ {
} }
@@ -68,7 +65,8 @@ public class SonarrClient : ArrClient, ISonarrClient
try try
{ {
using var _ = await ((SonarrClient)Proxy).SendRequestAsync(request); HttpResponseMessage? response = await _dryRunInterceptor.InterceptAsync<HttpResponseMessage>(SendRequestAsync, request);
response?.Dispose();
_logger.LogInformation("{log}", GetSearchLog(command.SearchType, arrInstance.Url, command, true, logContext)); _logger.LogInformation("{log}", GetSearchLog(command.SearchType, arrInstance.Url, command, true, logContext));
} }
@@ -36,7 +36,7 @@ public sealed class ContentBlocker : GenericHandler
ArrQueueIterator arrArrQueueIterator, ArrQueueIterator arrArrQueueIterator,
BlocklistProvider blocklistProvider, BlocklistProvider blocklistProvider,
DownloadServiceFactory downloadServiceFactory, DownloadServiceFactory downloadServiceFactory,
NotificationPublisher notifier INotificationPublisher notifier
) : base( ) : base(
logger, downloadClientConfig, logger, downloadClientConfig,
sonarrConfig, radarrConfig, lidarrConfig, sonarrConfig, radarrConfig, lidarrConfig,
@@ -31,7 +31,7 @@ public sealed class DownloadCleaner : GenericHandler
LidarrClient lidarrClient, LidarrClient lidarrClient,
ArrQueueIterator arrArrQueueIterator, ArrQueueIterator arrArrQueueIterator,
DownloadServiceFactory downloadServiceFactory, DownloadServiceFactory downloadServiceFactory,
NotificationPublisher notifier INotificationPublisher notifier
) : base( ) : base(
logger, downloadClientConfig, logger, downloadClientConfig,
sonarrConfig, radarrConfig, lidarrConfig, sonarrConfig, radarrConfig, lidarrConfig,
@@ -7,11 +7,11 @@ using Common.Configuration.DownloadClient;
using Common.Configuration.QueueCleaner; using Common.Configuration.QueueCleaner;
using Domain.Enums; using Domain.Enums;
using Domain.Models.Deluge.Response; using Domain.Models.Deluge.Response;
using Infrastructure.Interceptors;
using Infrastructure.Verticals.ContentBlocker; using Infrastructure.Verticals.ContentBlocker;
using Infrastructure.Verticals.Context; using Infrastructure.Verticals.Context;
using Infrastructure.Verticals.ItemStriker; using Infrastructure.Verticals.ItemStriker;
using Infrastructure.Verticals.Notifications; using Infrastructure.Verticals.Notifications;
using MassTransit.Configuration;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@@ -22,11 +22,6 @@ public class DelugeService : DownloadService, IDelugeService
{ {
private readonly DelugeClient _client; private readonly DelugeClient _client;
/// <inheritdoc/>
public DelugeService()
{
}
public DelugeService( public DelugeService(
ILogger<DelugeService> logger, ILogger<DelugeService> logger,
IOptions<DelugeConfig> config, IOptions<DelugeConfig> config,
@@ -37,8 +32,12 @@ public class DelugeService : DownloadService, IDelugeService
IMemoryCache cache, IMemoryCache cache,
IFilenameEvaluator filenameEvaluator, IFilenameEvaluator filenameEvaluator,
IStriker striker, IStriker striker,
NotificationPublisher notifier INotificationPublisher notifier,
) : base(logger, queueCleanerConfig, contentBlockerConfig, downloadCleanerConfig, cache, filenameEvaluator, striker, notifier) IDryRunInterceptor dryRunInterceptor
) : base(
logger, queueCleanerConfig, contentBlockerConfig, downloadCleanerConfig, cache,
filenameEvaluator, striker, notifier, dryRunInterceptor
)
{ {
config.Value.Validate(); config.Value.Validate();
_client = new (config, httpClientFactory); _client = new (config, httpClientFactory);
@@ -190,7 +189,7 @@ public class DelugeService : DownloadService, IDelugeService
return result; return result;
} }
await ((DelugeService)Proxy).ChangeFilesPriority(hash, sortedPriorities); await _dryRunInterceptor.InterceptAsync(ChangeFilesPriority, hash, sortedPriorities);
return result; return result;
} }
@@ -246,7 +245,7 @@ public class DelugeService : DownloadService, IDelugeService
continue; continue;
} }
await ((DelugeService)Proxy).DeleteDownload(download.Hash); await _dryRunInterceptor.InterceptAsync(DeleteDownload, download.Hash);
_logger.LogInformation( _logger.LogInformation(
"download cleaned | {reason} reached | {name}", "download cleaned | {reason} reached | {name}",
@@ -18,7 +18,7 @@ using Microsoft.Extensions.Options;
namespace Infrastructure.Verticals.DownloadClient; namespace Infrastructure.Verticals.DownloadClient;
public abstract class DownloadService : InterceptedService, IDownloadService public abstract class DownloadService : IDownloadService
{ {
protected readonly ILogger<DownloadService> _logger; protected readonly ILogger<DownloadService> _logger;
protected readonly QueueCleanerConfig _queueCleanerConfig; protected readonly QueueCleanerConfig _queueCleanerConfig;
@@ -28,14 +28,8 @@ public abstract class DownloadService : InterceptedService, IDownloadService
protected readonly IFilenameEvaluator _filenameEvaluator; protected readonly IFilenameEvaluator _filenameEvaluator;
protected readonly IStriker _striker; protected readonly IStriker _striker;
protected readonly MemoryCacheEntryOptions _cacheOptions; protected readonly MemoryCacheEntryOptions _cacheOptions;
protected readonly NotificationPublisher _notifier; protected readonly INotificationPublisher _notifier;
protected readonly IDryRunInterceptor _dryRunInterceptor;
/// <summary>
/// Constructor to be used by interceptors.
/// </summary>
protected DownloadService()
{
}
protected DownloadService( protected DownloadService(
ILogger<DownloadService> logger, ILogger<DownloadService> logger,
@@ -45,7 +39,9 @@ public abstract class DownloadService : InterceptedService, IDownloadService
IMemoryCache cache, IMemoryCache cache,
IFilenameEvaluator filenameEvaluator, IFilenameEvaluator filenameEvaluator,
IStriker striker, IStriker striker,
NotificationPublisher notifier) INotificationPublisher notifier,
IDryRunInterceptor dryRunInterceptor
)
{ {
_logger = logger; _logger = logger;
_queueCleanerConfig = queueCleanerConfig.Value; _queueCleanerConfig = queueCleanerConfig.Value;
@@ -55,6 +51,7 @@ public abstract class DownloadService : InterceptedService, IDownloadService
_filenameEvaluator = filenameEvaluator; _filenameEvaluator = filenameEvaluator;
_striker = striker; _striker = striker;
_notifier = notifier; _notifier = notifier;
_dryRunInterceptor = dryRunInterceptor;
_cacheOptions = new MemoryCacheEntryOptions() _cacheOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(StaticConfiguration.TriggerValue + Constants.CacheLimitBuffer); .SetSlidingExpiration(StaticConfiguration.TriggerValue + Constants.CacheLimitBuffer);
} }
@@ -3,6 +3,7 @@ using System.Text.RegularExpressions;
using Common.Configuration.ContentBlocker; using Common.Configuration.ContentBlocker;
using Common.Configuration.DownloadCleaner; using Common.Configuration.DownloadCleaner;
using Common.Configuration.QueueCleaner; using Common.Configuration.QueueCleaner;
using Infrastructure.Interceptors;
using Infrastructure.Verticals.ContentBlocker; using Infrastructure.Verticals.ContentBlocker;
using Infrastructure.Verticals.ItemStriker; using Infrastructure.Verticals.ItemStriker;
using Infrastructure.Verticals.Notifications; using Infrastructure.Verticals.Notifications;
@@ -14,12 +15,7 @@ namespace Infrastructure.Verticals.DownloadClient;
public class DummyDownloadService : DownloadService public class DummyDownloadService : DownloadService
{ {
/// <inheritdoc/> public DummyDownloadService(ILogger<DownloadService> logger, IOptions<QueueCleanerConfig> queueCleanerConfig, IOptions<ContentBlockerConfig> contentBlockerConfig, IOptions<DownloadCleanerConfig> downloadCleanerConfig, IMemoryCache cache, IFilenameEvaluator filenameEvaluator, IStriker striker, INotificationPublisher notifier, IDryRunInterceptor dryRunInterceptor) : base(logger, queueCleanerConfig, contentBlockerConfig, downloadCleanerConfig, cache, filenameEvaluator, striker, notifier, dryRunInterceptor)
public DummyDownloadService()
{
}
public DummyDownloadService(ILogger<DownloadService> logger, IOptions<QueueCleanerConfig> queueCleanerConfig, IOptions<ContentBlockerConfig> contentBlockerConfig, IOptions<DownloadCleanerConfig> downloadCleanerConfig, IMemoryCache cache, IFilenameEvaluator filenameEvaluator, IStriker striker, NotificationPublisher notifier) : base(logger, queueCleanerConfig, contentBlockerConfig, downloadCleanerConfig, cache, filenameEvaluator, striker, notifier)
{ {
} }
@@ -6,7 +6,7 @@ using Infrastructure.Interceptors;
namespace Infrastructure.Verticals.DownloadClient; namespace Infrastructure.Verticals.DownloadClient;
public interface IDownloadService : IDisposable, IDryRunService public interface IDownloadService : IDisposable
{ {
public Task LoginAsync(); public Task LoginAsync();
@@ -1,5 +1,5 @@
namespace Infrastructure.Verticals.DownloadClient.QBittorrent; namespace Infrastructure.Verticals.DownloadClient.QBittorrent;
public interface IQBitService : IDownloadService public interface IQBitService : IDownloadService, IDisposable
{ {
} }
@@ -7,6 +7,7 @@ using Common.Configuration.DownloadClient;
using Common.Configuration.QueueCleaner; using Common.Configuration.QueueCleaner;
using Common.Helpers; using Common.Helpers;
using Domain.Enums; using Domain.Enums;
using Infrastructure.Interceptors;
using Infrastructure.Verticals.ContentBlocker; using Infrastructure.Verticals.ContentBlocker;
using Infrastructure.Verticals.Context; using Infrastructure.Verticals.Context;
using Infrastructure.Verticals.ItemStriker; using Infrastructure.Verticals.ItemStriker;
@@ -24,11 +25,6 @@ public class QBitService : DownloadService, IQBitService
private readonly QBitConfig _config; private readonly QBitConfig _config;
private readonly QBittorrentClient _client; private readonly QBittorrentClient _client;
/// <inheritdoc/>
public QBitService()
{
}
public QBitService( public QBitService(
ILogger<QBitService> logger, ILogger<QBitService> logger,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
@@ -39,8 +35,12 @@ public class QBitService : DownloadService, IQBitService
IMemoryCache cache, IMemoryCache cache,
IFilenameEvaluator filenameEvaluator, IFilenameEvaluator filenameEvaluator,
IStriker striker, IStriker striker,
NotificationPublisher notifier INotificationPublisher notifier,
) : base(logger, queueCleanerConfig, contentBlockerConfig, downloadCleanerConfig, cache, filenameEvaluator, striker, notifier) IDryRunInterceptor dryRunInterceptor
) : base(
logger, queueCleanerConfig, contentBlockerConfig, downloadCleanerConfig, cache,
filenameEvaluator, striker, notifier, dryRunInterceptor
)
{ {
_config = config.Value; _config = config.Value;
_config.Validate(); _config.Validate();
@@ -200,7 +200,7 @@ public class QBitService : DownloadService, IQBitService
foreach (int fileIndex in unwantedFiles) foreach (int fileIndex in unwantedFiles)
{ {
await ((QBitService)Proxy).SkipFile(hash, fileIndex); await _dryRunInterceptor.InterceptAsync(SkipFile, hash, fileIndex);
} }
return result; return result;
@@ -272,7 +272,7 @@ public class QBitService : DownloadService, IQBitService
continue; continue;
} }
await ((QBitService)Proxy).DeleteDownload(download.Hash); await _dryRunInterceptor.InterceptAsync(DeleteDownload, download.Hash);
_logger.LogInformation( _logger.LogInformation(
"download cleaned | {reason} reached | {name}", "download cleaned | {reason} reached | {name}",
@@ -7,6 +7,7 @@ using Common.Configuration.DownloadClient;
using Common.Configuration.QueueCleaner; using Common.Configuration.QueueCleaner;
using Common.Helpers; using Common.Helpers;
using Domain.Enums; using Domain.Enums;
using Infrastructure.Interceptors;
using Infrastructure.Verticals.ContentBlocker; using Infrastructure.Verticals.ContentBlocker;
using Infrastructure.Verticals.Context; using Infrastructure.Verticals.Context;
using Infrastructure.Verticals.ItemStriker; using Infrastructure.Verticals.ItemStriker;
@@ -26,11 +27,6 @@ public class TransmissionService : DownloadService, ITransmissionService
private readonly Client _client; private readonly Client _client;
private TorrentInfo[]? _torrentsCache; private TorrentInfo[]? _torrentsCache;
/// <inheritdoc/>
public TransmissionService()
{
}
public TransmissionService( public TransmissionService(
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
ILogger<TransmissionService> logger, ILogger<TransmissionService> logger,
@@ -41,8 +37,12 @@ public class TransmissionService : DownloadService, ITransmissionService
IMemoryCache cache, IMemoryCache cache,
IFilenameEvaluator filenameEvaluator, IFilenameEvaluator filenameEvaluator,
IStriker striker, IStriker striker,
NotificationPublisher notifier INotificationPublisher notifier,
) : base(logger, queueCleanerConfig, contentBlockerConfig, downloadCleanerConfig, cache, filenameEvaluator, striker, notifier) IDryRunInterceptor dryRunInterceptor
) : base(
logger, queueCleanerConfig, contentBlockerConfig, downloadCleanerConfig, cache,
filenameEvaluator, striker, notifier, dryRunInterceptor
)
{ {
_config = config.Value; _config = config.Value;
_config.Validate(); _config.Validate();
@@ -175,7 +175,7 @@ public class TransmissionService : DownloadService, ITransmissionService
_logger.LogDebug("changing priorities | torrent {hash}", hash); _logger.LogDebug("changing priorities | torrent {hash}", hash);
await ((TransmissionService)Proxy).SetUnwantedFiles(torrent.Id, unwantedFiles.ToArray()); await _dryRunInterceptor.InterceptAsync(SetUnwantedFiles, torrent.Id, unwantedFiles.ToArray());
return result; return result;
} }
@@ -268,7 +268,7 @@ public class TransmissionService : DownloadService, ITransmissionService
continue; continue;
} }
await ((TransmissionService)Proxy).RemoveDownloadAsync(download.Id); await _dryRunInterceptor.InterceptAsync(RemoveDownloadAsync, download.Id);
_logger.LogInformation( _logger.LogInformation(
"download cleaned | {reason} reached | {name}", "download cleaned | {reason} reached | {name}",
@@ -1,6 +1,7 @@
using Common.Helpers; using Common.Helpers;
using Domain.Enums; using Domain.Enums;
using Infrastructure.Helpers; using Infrastructure.Helpers;
using Infrastructure.Interceptors;
using Infrastructure.Verticals.Context; using Infrastructure.Verticals.Context;
using Infrastructure.Verticals.Notifications; using Infrastructure.Verticals.Notifications;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
@@ -13,13 +14,15 @@ public sealed class Striker : IStriker
private readonly ILogger<Striker> _logger; private readonly ILogger<Striker> _logger;
private readonly IMemoryCache _cache; private readonly IMemoryCache _cache;
private readonly MemoryCacheEntryOptions _cacheOptions; private readonly MemoryCacheEntryOptions _cacheOptions;
private readonly NotificationPublisher _notifier; private readonly INotificationPublisher _notifier;
private readonly IDryRunInterceptor _dryRunInterceptor;
public Striker(ILogger<Striker> logger, IMemoryCache cache, NotificationPublisher notifier) public Striker(ILogger<Striker> logger, IMemoryCache cache, INotificationPublisher notifier, IDryRunInterceptor dryRunInterceptor)
{ {
_logger = logger; _logger = logger;
_cache = cache; _cache = cache;
_notifier = notifier; _notifier = notifier;
_dryRunInterceptor = dryRunInterceptor;
_cacheOptions = new MemoryCacheEntryOptions() _cacheOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(StaticConfiguration.TriggerValue + Constants.CacheLimitBuffer); .SetSlidingExpiration(StaticConfiguration.TriggerValue + Constants.CacheLimitBuffer);
} }
@@ -24,7 +24,7 @@ public abstract class GenericHandler : IHandler, IDisposable
protected readonly ILidarrClient _lidarrClient; protected readonly ILidarrClient _lidarrClient;
protected readonly ArrQueueIterator _arrArrQueueIterator; protected readonly ArrQueueIterator _arrArrQueueIterator;
protected readonly IDownloadService _downloadService; protected readonly IDownloadService _downloadService;
protected readonly NotificationPublisher _notifier; protected readonly INotificationPublisher _notifier;
protected GenericHandler( protected GenericHandler(
ILogger<GenericHandler> logger, ILogger<GenericHandler> logger,
@@ -37,7 +37,7 @@ public abstract class GenericHandler : IHandler, IDisposable
ILidarrClient lidarrClient, ILidarrClient lidarrClient,
ArrQueueIterator arrArrQueueIterator, ArrQueueIterator arrArrQueueIterator,
DownloadServiceFactory downloadServiceFactory, DownloadServiceFactory downloadServiceFactory,
NotificationPublisher notifier INotificationPublisher notifier
) )
{ {
_logger = logger; _logger = logger;
@@ -0,0 +1,12 @@
using Domain.Enums;
namespace Infrastructure.Verticals.Notifications;
public interface INotificationPublisher
{
Task NotifyStrike(StrikeType strikeType, int strikeCount);
Task NotifyQueueItemDeleted(bool removeFromClient, DeleteReason reason);
Task NotifyDownloadCleaned(double ratio, TimeSpan seedingTime, string categoryName, CleanReason reason);
}
@@ -12,25 +12,19 @@ using Microsoft.Extensions.Logging;
namespace Infrastructure.Verticals.Notifications; namespace Infrastructure.Verticals.Notifications;
public class NotificationPublisher : InterceptedService, IDryRunService public class NotificationPublisher : INotificationPublisher
{ {
private readonly ILogger<NotificationPublisher> _logger; private readonly ILogger<INotificationPublisher> _logger;
private readonly IBus _messageBus; private readonly IBus _messageBus;
private readonly IDryRunInterceptor _dryRunInterceptor;
/// <summary> public NotificationPublisher(ILogger<INotificationPublisher> logger, IBus messageBus, IDryRunInterceptor dryRunInterceptor)
/// Constructor to be used by interceptors.
/// </summary>
public NotificationPublisher()
{
}
public NotificationPublisher(ILogger<NotificationPublisher> logger, IBus messageBus)
{ {
_logger = logger; _logger = logger;
_messageBus = messageBus; _messageBus = messageBus;
_dryRunInterceptor = dryRunInterceptor;
} }
[DryRunSafeguard]
public virtual async Task NotifyStrike(StrikeType strikeType, int strikeCount) public virtual async Task NotifyStrike(StrikeType strikeType, int strikeCount)
{ {
try try
@@ -54,10 +48,10 @@ public class NotificationPublisher : InterceptedService, IDryRunService
switch (strikeType) switch (strikeType)
{ {
case StrikeType.Stalled: case StrikeType.Stalled:
await _messageBus.Publish(notification.Adapt<StalledStrikeNotification>()); await _dryRunInterceptor.InterceptAsync(Notify<StalledStrikeNotification>, notification.Adapt<StalledStrikeNotification>());
break; break;
case StrikeType.ImportFailed: case StrikeType.ImportFailed:
await _messageBus.Publish(notification.Adapt<FailedImportStrikeNotification>()); await _dryRunInterceptor.InterceptAsync(Notify<FailedImportStrikeNotification>, notification.Adapt<FailedImportStrikeNotification>());
break; break;
} }
} }
@@ -67,7 +61,6 @@ public class NotificationPublisher : InterceptedService, IDryRunService
} }
} }
[DryRunSafeguard]
public virtual async Task NotifyQueueItemDeleted(bool removeFromClient, DeleteReason reason) public virtual async Task NotifyQueueItemDeleted(bool removeFromClient, DeleteReason reason)
{ {
QueueRecord record = ContextProvider.Get<QueueRecord>(nameof(QueueRecord)); QueueRecord record = ContextProvider.Get<QueueRecord>(nameof(QueueRecord));
@@ -86,10 +79,9 @@ public class NotificationPublisher : InterceptedService, IDryRunService
Fields = [new() { Title = "Removed from download client?", Text = removeFromClient ? "Yes" : "No" }] Fields = [new() { Title = "Removed from download client?", Text = removeFromClient ? "Yes" : "No" }]
}; };
await _messageBus.Publish(notification); await _dryRunInterceptor.InterceptAsync(Notify<QueueItemDeletedNotification>, notification);
} }
[DryRunSafeguard]
public virtual async Task NotifyDownloadCleaned(double ratio, TimeSpan seedingTime, string categoryName, CleanReason reason) public virtual async Task NotifyDownloadCleaned(double ratio, TimeSpan seedingTime, string categoryName, CleanReason reason)
{ {
DownloadCleanedNotification notification = new() DownloadCleanedNotification notification = new()
@@ -106,7 +98,13 @@ public class NotificationPublisher : InterceptedService, IDryRunService
Level = NotificationLevel.Important Level = NotificationLevel.Important
}; };
await _messageBus.Publish(notification); await _dryRunInterceptor.InterceptAsync(Notify<DownloadCleanedNotification>, notification);
}
[DryRunSafeguard]
private Task Notify<T>(T message) where T: notnull
{
return _messageBus.Publish(message);
} }
private static Uri GetImageFromContext(QueueRecord record, InstanceType instanceType) => private static Uri GetImageFromContext(QueueRecord record, InstanceType instanceType) =>
@@ -32,7 +32,7 @@ public sealed class QueueCleaner : GenericHandler
LidarrClient lidarrClient, LidarrClient lidarrClient,
ArrQueueIterator arrArrQueueIterator, ArrQueueIterator arrArrQueueIterator,
DownloadServiceFactory downloadServiceFactory, DownloadServiceFactory downloadServiceFactory,
NotificationPublisher notifier INotificationPublisher notifier
) : base( ) : base(
logger, downloadClientConfig, logger, downloadClientConfig,
sonarrConfig, radarrConfig, lidarrConfig, sonarrConfig, radarrConfig, lidarrConfig,