Compare commits

...

5 Commits

Author SHA1 Message Date
Flaminel ba02aa0e49 Fix notifications failing when poster image is not set (#78) 2025-03-02 22:48:21 +02:00
Flaminel 5adbdbd920 Fix weird time zone display name on startup (#70) 2025-02-25 21:32:19 +02:00
Flaminel b3b211d956 Add configurable time zone (#69) 2025-02-24 23:21:44 +02:00
Flaminel 279bd6d82d Fix Deluge timeout not being configurable (#68) 2025-02-24 18:32:44 +02:00
Flaminel 5dced28228 fixed errors on download cleaner when download client is none (#67) 2025-02-24 12:43:06 +02:00
8 changed files with 100 additions and 45 deletions
+1
View File
@@ -140,6 +140,7 @@ services:
volumes: volumes:
- ./cleanuperr/logs:/var/logs - ./cleanuperr/logs:/var/logs
environment: environment:
- TZ=America/New_York
- DRY_RUN=false - DRY_RUN=false
- LOGGING__LOGLEVEL=Information - LOGGING__LOGLEVEL=Information
@@ -62,7 +62,7 @@ public static class MainDI
services services
.AddHttpClient(nameof(DelugeService), x => .AddHttpClient(nameof(DelugeService), x =>
{ {
x.Timeout = TimeSpan.FromSeconds(5); x.Timeout = TimeSpan.FromSeconds(config.Timeout);
}) })
.ConfigurePrimaryHttpMessageHandler(_ => .ConfigurePrimaryHttpMessageHandler(_ =>
{ {
+23
View File
@@ -0,0 +1,23 @@
using System.Reflection;
namespace Executable;
public static class HostExtensions
{
public static IHost Init(this IHost host)
{
ILogger<Program> logger = host.Services.GetRequiredService<ILogger<Program>>();
Version? version = Assembly.GetExecutingAssembly().GetName().Version;
logger.LogInformation(
version is null
? "cleanuperr version not detected"
: $"cleanuperr v{version.Major}.{version.Minor}.{version.Build}"
);
logger.LogInformation("timezone: {tz}", TimeZoneInfo.Local.DisplayName);
return host;
}
}
+2 -11
View File
@@ -1,4 +1,4 @@
using System.Reflection; using Executable;
using Executable.DependencyInjection; using Executable.DependencyInjection;
var builder = Host.CreateApplicationBuilder(args); var builder = Host.CreateApplicationBuilder(args);
@@ -7,15 +7,6 @@ builder.Services.AddInfrastructure(builder.Configuration);
builder.Logging.AddLogging(builder.Configuration); builder.Logging.AddLogging(builder.Configuration);
var host = builder.Build(); var host = builder.Build();
host.Init();
var logger = host.Services.GetRequiredService<ILogger<Program>>();
var version = Assembly.GetExecutingAssembly().GetName().Version;
logger.LogInformation(
version is null
? "cleanuperr version not detected"
: $"cleanuperr v{version.Major}.{version.Minor}.{version.Build}"
);
host.Run(); host.Run();
@@ -46,6 +46,12 @@ public sealed class DownloadCleaner : GenericHandler
public override async Task ExecuteAsync() public override async Task ExecuteAsync()
{ {
if (_downloadClientConfig.DownloadClient is Common.Enums.DownloadClient.None)
{
_logger.LogWarning("download client is set to none");
return;
}
if (_config.Categories?.Count is null or 0) if (_config.Categories?.Count is null or 0)
{ {
_logger.LogWarning("no categories configured"); _logger.LogWarning("no categories configured");
@@ -32,7 +32,7 @@ public class NotificationPublisher : INotificationPublisher
QueueRecord record = ContextProvider.Get<QueueRecord>(nameof(QueueRecord)); QueueRecord record = ContextProvider.Get<QueueRecord>(nameof(QueueRecord));
InstanceType instanceType = (InstanceType)ContextProvider.Get<object>(nameof(InstanceType)); InstanceType instanceType = (InstanceType)ContextProvider.Get<object>(nameof(InstanceType));
Uri instanceUrl = ContextProvider.Get<Uri>(nameof(ArrInstance) + nameof(ArrInstance.Url)); Uri instanceUrl = ContextProvider.Get<Uri>(nameof(ArrInstance) + nameof(ArrInstance.Url));
Uri imageUrl = GetImageFromContext(record, instanceType); Uri? imageUrl = GetImageFromContext(record, instanceType);
ArrNotification notification = new() ArrNotification notification = new()
{ {
@@ -63,42 +63,59 @@ public class NotificationPublisher : INotificationPublisher
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)); try
InstanceType instanceType = (InstanceType)ContextProvider.Get<object>(nameof(InstanceType));
Uri instanceUrl = ContextProvider.Get<Uri>(nameof(ArrInstance) + nameof(ArrInstance.Url));
Uri imageUrl = GetImageFromContext(record, instanceType);
QueueItemDeletedNotification notification = new()
{ {
InstanceType = instanceType, QueueRecord record = ContextProvider.Get<QueueRecord>(nameof(QueueRecord));
InstanceUrl = instanceUrl, InstanceType instanceType = (InstanceType)ContextProvider.Get<object>(nameof(InstanceType));
Hash = record.DownloadId.ToLowerInvariant(), Uri instanceUrl = ContextProvider.Get<Uri>(nameof(ArrInstance) + nameof(ArrInstance.Url));
Title = $"Deleting item from queue with reason: {reason}", Uri? imageUrl = GetImageFromContext(record, instanceType);
Description = record.Title,
Image = imageUrl, QueueItemDeletedNotification notification = new()
Fields = [new() { Title = "Removed from download client?", Text = removeFromClient ? "Yes" : "No" }] {
}; InstanceType = instanceType,
InstanceUrl = instanceUrl,
await _dryRunInterceptor.InterceptAsync(Notify<QueueItemDeletedNotification>, notification); Hash = record.DownloadId.ToLowerInvariant(),
Title = $"Deleting item from queue with reason: {reason}",
Description = record.Title,
Image = imageUrl,
Fields = [new() { Title = "Removed from download client?", Text = removeFromClient ? "Yes" : "No" }]
};
await _dryRunInterceptor.InterceptAsync(Notify<QueueItemDeletedNotification>, notification);
}
catch (Exception ex)
{
_logger.LogError(ex, "failed to notify queue item deleted");
}
} }
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() try
{ {
Title = $"Cleaned item from download client with reason: {reason}", DownloadCleanedNotification notification = new()
Description = ContextProvider.Get<string>("downloadName"), {
Fields = Title = $"Cleaned item from download client with reason: {reason}",
[ Description = ContextProvider.Get<string>("downloadName"),
new() { Title = "Hash", Text = ContextProvider.Get<string>("hash").ToLowerInvariant() }, Fields =
new() { Title = "Category", Text = categoryName.ToLowerInvariant() }, [
new() { Title = "Ratio", Text = $"{ratio.ToString(CultureInfo.InvariantCulture)}%" }, new() { Title = "Hash", Text = ContextProvider.Get<string>("hash").ToLowerInvariant() },
new() { Title = "Seeding hours", Text = $"{Math.Round(seedingTime.TotalHours, 0).ToString(CultureInfo.InvariantCulture)}h" } new() { Title = "Category", Text = categoryName.ToLowerInvariant() },
], new() { Title = "Ratio", Text = $"{ratio.ToString(CultureInfo.InvariantCulture)}%" },
Level = NotificationLevel.Important new()
}; {
Title = "Seeding hours", Text = $"{Math.Round(seedingTime.TotalHours, 0).ToString(CultureInfo.InvariantCulture)}h"
}
],
Level = NotificationLevel.Important
};
await _dryRunInterceptor.InterceptAsync(Notify<DownloadCleanedNotification>, notification); await _dryRunInterceptor.InterceptAsync(Notify<DownloadCleanedNotification>, notification);
}
catch (Exception ex)
{
_logger.LogError(ex, "failed to notify download cleaned");
}
} }
[DryRunSafeguard] [DryRunSafeguard]
@@ -107,12 +124,21 @@ public class NotificationPublisher : INotificationPublisher
return _messageBus.Publish(message); return _messageBus.Publish(message);
} }
private static Uri GetImageFromContext(QueueRecord record, InstanceType instanceType) => private Uri? GetImageFromContext(QueueRecord record, InstanceType instanceType)
instanceType switch {
Uri? image = instanceType switch
{ {
InstanceType.Sonarr => record.Series!.Images.FirstOrDefault(x => x.CoverType == "poster")?.RemoteUrl, InstanceType.Sonarr => record.Series!.Images.FirstOrDefault(x => x.CoverType == "poster")?.RemoteUrl,
InstanceType.Radarr => record.Movie!.Images.FirstOrDefault(x => x.CoverType == "poster")?.RemoteUrl, InstanceType.Radarr => record.Movie!.Images.FirstOrDefault(x => x.CoverType == "poster")?.RemoteUrl,
InstanceType.Lidarr => record.Album!.Images.FirstOrDefault(x => x.CoverType == "cover")?.Url, InstanceType.Lidarr => record.Album!.Images.FirstOrDefault(x => x.CoverType == "cover")?.Url,
_ => throw new ArgumentOutOfRangeException(nameof(instanceType)) _ => throw new ArgumentOutOfRangeException(nameof(instanceType))
} ?? throw new Exception("failed to get image url from context"); };
if (image is null)
{
_logger.LogWarning("no poster found for {title}", record.Title);
}
return image;
}
} }
+1
View File
@@ -175,6 +175,7 @@ services:
image: ghcr.io/flmorg/cleanuperr:latest image: ghcr.io/flmorg/cleanuperr:latest
container_name: cleanuperr container_name: cleanuperr
environment: environment:
- TZ=Europe/Bucharest
- DRY_RUN=false - DRY_RUN=false
- LOGGING__LOGLEVEL=Debug - LOGGING__LOGLEVEL=Debug
+7
View File
@@ -12,6 +12,13 @@
### General settings ### General settings
**`TZ`**
- The time zone to use.
- Type: String.
- Possible values: Any valid timezone.
- Default: `UTC`.
- Required: No.
**`DRY_RUN`** **`DRY_RUN`**
- When enabled, simulates irreversible operations (like deletions and notifications) without making actual changes. - When enabled, simulates irreversible operations (like deletions and notifications) without making actual changes.
- Type: Boolean. - Type: Boolean.