Add download cleaner and dry run (#58)

This commit is contained in:
Marius Nechifor
2025-02-16 03:09:07 +02:00
parent 19b3675701
commit 596a5aed8d
87 changed files with 2507 additions and 413 deletions

View File

@@ -27,9 +27,12 @@ public sealed class NotificationConsumer<T> : IConsumer<T> where T : Notificatio
case StalledStrikeNotification stalledMessage:
await _notificationService.Notify(stalledMessage);
break;
case QueueItemDeleteNotification queueItemDeleteMessage:
case QueueItemDeletedNotification queueItemDeleteMessage:
await _notificationService.Notify(queueItemDeleteMessage);
break;
case DownloadCleanedNotification downloadCleanedNotification:
await _notificationService.Notify(downloadCleanedNotification);
break;
default:
throw new NotImplementedException();
}

View File

@@ -6,5 +6,7 @@ public interface INotificationFactory
List<INotificationProvider> OnStalledStrikeEnabled();
List<INotificationProvider> OnQueueItemDeleteEnabled();
List<INotificationProvider> OnQueueItemDeletedEnabled();
List<INotificationProvider> OnDownloadCleanedEnabled();
}

View File

@@ -13,5 +13,7 @@ public interface INotificationProvider
Task OnStalledStrike(StalledStrikeNotification notification);
Task OnQueueItemDelete(QueueItemDeleteNotification notification);
Task OnQueueItemDeleted(QueueItemDeletedNotification notification);
Task OnDownloadCleaned(DownloadCleanedNotification notification);
}

View File

@@ -0,0 +1,14 @@
using Domain.Enums;
namespace Infrastructure.Verticals.Notifications.Models;
public record ArrNotification : Notification
{
public required InstanceType InstanceType { get; init; }
public required Uri InstanceUrl { get; init; }
public required string Hash { get; init; }
public Uri? Image { get; init; }
}

View File

@@ -0,0 +1,5 @@
namespace Infrastructure.Verticals.Notifications.Models;
public sealed record DownloadCleanedNotification : Notification
{
}

View File

@@ -1,5 +1,5 @@
namespace Infrastructure.Verticals.Notifications.Models;
public sealed record FailedImportStrikeNotification : Notification
public sealed record FailedImportStrikeNotification : ArrNotification
{
}

View File

@@ -1,20 +1,12 @@
using Domain.Enums;
namespace Infrastructure.Verticals.Notifications.Models;
namespace Infrastructure.Verticals.Notifications.Models;
public record Notification
public abstract record Notification
{
public required InstanceType InstanceType { get; init; }
public required Uri InstanceUrl { get; init; }
public required string Hash { get; init; }
public required string Title { get; init; }
public required string Description { get; init; }
public Uri? Image { get; init; }
public List<NotificationField>? Fields { get; init; }
public NotificationLevel Level { get; init; }
}

View File

@@ -0,0 +1,9 @@
namespace Infrastructure.Verticals.Notifications.Models;
public enum NotificationLevel
{
Test,
Information,
Warning,
Important
}

View File

@@ -1,5 +0,0 @@
namespace Infrastructure.Verticals.Notifications.Models;
public sealed record QueueItemDeleteNotification : Notification
{
}

View File

@@ -0,0 +1,5 @@
namespace Infrastructure.Verticals.Notifications.Models;
public sealed record QueueItemDeletedNotification : ArrNotification
{
}

View File

@@ -1,5 +1,5 @@
namespace Infrastructure.Verticals.Notifications.Models;
public sealed record StalledStrikeNotification : Notification
public sealed record StalledStrikeNotification : ArrNotification
{
}

View File

@@ -1,4 +1,3 @@
using Domain.Enums;
using Infrastructure.Verticals.Notifications.Models;
using Mapster;
using Microsoft.Extensions.Options;
@@ -12,6 +11,7 @@ public class NotifiarrProvider : NotificationProvider
private const string WarningColor = "f0ad4e";
private const string ImportantColor = "bb2124";
private const string Logo = "https://github.com/flmorg/cleanuperr/blob/main/Logo/48.png?raw=true";
public NotifiarrProvider(IOptions<NotifiarrConfig> config, INotifiarrProxy proxy)
: base(config)
@@ -32,12 +32,17 @@ public class NotifiarrProvider : NotificationProvider
await _proxy.SendNotification(BuildPayload(notification, WarningColor), _config);
}
public override async Task OnQueueItemDelete(QueueItemDeleteNotification notification)
public override async Task OnQueueItemDeleted(QueueItemDeletedNotification notification)
{
await _proxy.SendNotification(BuildPayload(notification, ImportantColor), _config);
}
private NotifiarrPayload BuildPayload(Notification notification, string color)
public override async Task OnDownloadCleaned(DownloadCleanedNotification notification)
{
await _proxy.SendNotification(BuildPayload(notification), _config);
}
private NotifiarrPayload BuildPayload(ArrNotification notification, string color)
{
NotifiarrPayload payload = new()
{
@@ -47,7 +52,7 @@ public class NotifiarrProvider : NotificationProvider
Text = new()
{
Title = notification.Title,
Icon = "https://github.com/flmorg/cleanuperr/blob/main/Logo/48.png?raw=true",
Icon = Logo,
Description = notification.Description,
Fields = new()
{
@@ -62,7 +67,7 @@ public class NotifiarrProvider : NotificationProvider
},
Images = new()
{
Thumbnail = new Uri("https://github.com/flmorg/cleanuperr/blob/main/Logo/48.png?raw=true"),
Thumbnail = new Uri(Logo),
Image = notification.Image
}
}
@@ -72,4 +77,32 @@ public class NotifiarrProvider : NotificationProvider
return payload;
}
private NotifiarrPayload BuildPayload(DownloadCleanedNotification notification)
{
NotifiarrPayload payload = new()
{
Discord = new()
{
Color = ImportantColor,
Text = new()
{
Title = notification.Title,
Icon = Logo,
Description = notification.Description,
Fields = notification.Fields?.Adapt<List<Field>>() ?? []
},
Ids = new Ids
{
Channel = _config.ChannelId
},
Images = new()
{
Thumbnail = new Uri(Logo)
}
}
};
return payload;
}
}

View File

@@ -25,8 +25,13 @@ public class NotificationFactory : INotificationFactory
.Where(n => n.Config.OnStalledStrike)
.ToList();
public List<INotificationProvider> OnQueueItemDeleteEnabled() =>
public List<INotificationProvider> OnQueueItemDeletedEnabled() =>
ActiveProviders()
.Where(n => n.Config.OnQueueItemDelete)
.Where(n => n.Config.OnQueueItemDeleted)
.ToList();
public List<INotificationProvider> OnDownloadCleanedEnabled() =>
ActiveProviders()
.Where(n => n.Config.OnDownloadCleaned)
.ToList();
}

View File

@@ -19,5 +19,7 @@ public abstract class NotificationProvider : INotificationProvider
public abstract Task OnStalledStrike(StalledStrikeNotification notification);
public abstract Task OnQueueItemDelete(QueueItemDeleteNotification notification);
public abstract Task OnQueueItemDeleted(QueueItemDeletedNotification notification);
public abstract Task OnDownloadCleaned(DownloadCleanedNotification notification);
}

View File

@@ -1,6 +1,9 @@
using Common.Configuration.Arr;
using System.Globalization;
using Common.Attributes;
using Common.Configuration.Arr;
using Domain.Enums;
using Domain.Models.Arr.Queue;
using Infrastructure.Interceptors;
using Infrastructure.Verticals.Context;
using Infrastructure.Verticals.Notifications.Models;
using Mapster;
@@ -9,27 +12,35 @@ using Microsoft.Extensions.Logging;
namespace Infrastructure.Verticals.Notifications;
public sealed class NotificationPublisher
public class NotificationPublisher : InterceptedService, IDryRunService
{
private readonly ILogger<NotificationPublisher> _logger;
private readonly IBus _messageBus;
/// <summary>
/// Constructor to be used by interceptors.
/// </summary>
public NotificationPublisher()
{
}
public NotificationPublisher(ILogger<NotificationPublisher> logger, IBus messageBus)
{
_logger = logger;
_messageBus = messageBus;
}
public async Task NotifyStrike(StrikeType strikeType, int strikeCount)
[DryRunSafeguard]
public virtual async Task NotifyStrike(StrikeType strikeType, int strikeCount)
{
try
{
QueueRecord record = GetRecordFromContext();
InstanceType instanceType = GetInstanceTypeFromContext();
Uri instanceUrl = GetInstanceUrlFromContext();
Uri? imageUrl = GetImageFromContext(record, instanceType);
QueueRecord record = ContextProvider.Get<QueueRecord>(nameof(QueueRecord));
InstanceType instanceType = (InstanceType)ContextProvider.Get<object>(nameof(InstanceType));
Uri instanceUrl = ContextProvider.Get<Uri>(nameof(ArrInstance) + nameof(ArrInstance.Url));
Uri imageUrl = GetImageFromContext(record, instanceType);
Notification notification = new()
ArrNotification notification = new()
{
InstanceType = instanceType,
InstanceUrl = instanceUrl,
@@ -56,14 +67,15 @@ public sealed class NotificationPublisher
}
}
public async Task NotifyQueueItemDelete(bool removeFromClient, DeleteReason reason)
[DryRunSafeguard]
public virtual async Task NotifyQueueItemDeleted(bool removeFromClient, DeleteReason reason)
{
QueueRecord record = GetRecordFromContext();
InstanceType instanceType = GetInstanceTypeFromContext();
Uri instanceUrl = GetInstanceUrlFromContext();
Uri? imageUrl = GetImageFromContext(record, instanceType);
QueueRecord record = ContextProvider.Get<QueueRecord>(nameof(QueueRecord));
InstanceType instanceType = (InstanceType)ContextProvider.Get<object>(nameof(InstanceType));
Uri instanceUrl = ContextProvider.Get<Uri>(nameof(ArrInstance) + nameof(ArrInstance.Url));
Uri imageUrl = GetImageFromContext(record, instanceType);
Notification notification = new()
QueueItemDeletedNotification notification = new()
{
InstanceType = instanceType,
InstanceUrl = instanceUrl,
@@ -74,20 +86,29 @@ public sealed class NotificationPublisher
Fields = [new() { Title = "Removed from download client?", Text = removeFromClient ? "Yes" : "No" }]
};
await _messageBus.Publish(notification.Adapt<QueueItemDeleteNotification>());
await _messageBus.Publish(notification);
}
private static QueueRecord GetRecordFromContext() =>
ContextProvider.Get<QueueRecord>(nameof(QueueRecord)) ?? throw new Exception("failed to get record from context");
[DryRunSafeguard]
public virtual async Task NotifyDownloadCleaned(double ratio, TimeSpan seedingTime, string categoryName, CleanReason reason)
{
DownloadCleanedNotification notification = new()
{
Title = $"Cleaned item from download client with reason: {reason}",
Description = ContextProvider.Get<string>("downloadName"),
Fields =
[
new() { Title = "Hash", Text = ContextProvider.Get<string>("hash").ToLowerInvariant() },
new() { Title = "Category", Text = categoryName.ToLowerInvariant() },
new() { Title = "Ratio", Text = $"{ratio.ToString(CultureInfo.InvariantCulture)}%" },
new() { Title = "Seeding hours", Text = $"{Math.Round(seedingTime.TotalHours, 0).ToString(CultureInfo.InvariantCulture)}h" }
],
Level = NotificationLevel.Important
};
await _messageBus.Publish(notification);
}
private static InstanceType GetInstanceTypeFromContext() =>
(InstanceType)(ContextProvider.Get<object>(nameof(InstanceType)) ??
throw new Exception("failed to get instance type from context"));
private static Uri GetInstanceUrlFromContext() =>
ContextProvider.Get<Uri>(nameof(ArrInstance) + nameof(ArrInstance.Url)) ??
throw new Exception("failed to get instance url from context");
private static Uri GetImageFromContext(QueueRecord record, InstanceType instanceType) =>
instanceType switch
{

View File

@@ -44,13 +44,28 @@ public class NotificationService
}
}
public async Task Notify(QueueItemDeleteNotification notification)
public async Task Notify(QueueItemDeletedNotification notification)
{
foreach (INotificationProvider provider in _notificationFactory.OnQueueItemDeleteEnabled())
foreach (INotificationProvider provider in _notificationFactory.OnQueueItemDeletedEnabled())
{
try
{
await provider.OnQueueItemDelete(notification);
await provider.OnQueueItemDeleted(notification);
}
catch (Exception exception)
{
_logger.LogWarning(exception, "failed to send notification | provider {provider}", provider.Name);
}
}
}
public async Task Notify(DownloadCleanedNotification notification)
{
foreach (INotificationProvider provider in _notificationFactory.OnDownloadCleanedEnabled())
{
try
{
await provider.OnDownloadCleaned(notification);
}
catch (Exception exception)
{