Files
Daybreak/Daybreak.Core/Services/Notifications/NotificationService.cs
T

213 lines
8.0 KiB
C#

using Daybreak.Services.Notifications.Handlers;
using Daybreak.Services.Notifications.Models;
using Daybreak.Shared.Models.Notifications;
using Daybreak.Shared.Models.Notifications.Handling;
using Daybreak.Shared.Services.Notifications;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Core.Extensions;
using System.Extensions;
using System.Extensions.Core;
using System.Runtime.CompilerServices;
namespace Daybreak.Services.Notifications;
internal sealed class NotificationService(
IServiceProvider serviceProvider,
INotificationStorage notificationStorage,
ILogger<NotificationService> logger) : INotificationService, INotificationProducer
{
private readonly ConcurrentQueue<Notification> pendingNotifications = new();
// Using lazy rezolving to break dependency cycle
private readonly IServiceProvider serviceProvider = serviceProvider.ThrowIfNull();
private readonly INotificationStorage storage = notificationStorage.ThrowIfNull();
private readonly ILogger<NotificationService> logger = logger.ThrowIfNull();
public NotificationToken NotifyInformation(
string title,
string description,
string? metaData = default,
DateTime? expirationTime = default,
bool dismissible = true,
bool persistent = true)
{
return this.NotifyInternal<NoActionHandler>(title, description, metaData, expirationTime, dismissible, LogLevel.Information, persistent);
}
public NotificationToken NotifyError(
string title,
string description,
string? metaData = default,
DateTime? expirationTime = default,
bool dismissible = true,
bool persistent = true)
{
return this.NotifyInternal<NoActionHandler>(title, description, metaData, expirationTime, dismissible, LogLevel.Error, persistent);
}
public NotificationToken NotifyInformation<THandlingType>(
string title,
string description,
string? metaData = default,
DateTime? expirationTime = default,
bool dismissible = true,
bool persistent = true)
where THandlingType : class, INotificationHandler
{
return this.NotifyInternal<THandlingType>(title, description, metaData, expirationTime, dismissible, LogLevel.Information, persistent);
}
public NotificationToken NotifyError<THandlingType>(string title,
string description,
string? metaData = default,
DateTime? expirationTime = default,
bool dismissible = true,
bool persistent = true)
where THandlingType : class, INotificationHandler
{
return this.NotifyInternal<THandlingType>(title, description, metaData, expirationTime, dismissible, LogLevel.Error, persistent);
}
async Task INotificationProducer.OpenNotification(Notification notification, bool storeNotification, CancellationToken cancellationToken)
{
if (storeNotification)
{
try
{
await this.storage.OpenNotification(new NotificationDTO
{
Title = notification.Title,
Description = notification.Description,
Id = notification.Id,
Level = (int)notification.Level,
MetaData = notification.Metadata,
HandlerType = notification.HandlingType?.AssemblyQualifiedName,
ExpirationTime = notification.ExpirationTime.ToBinary(),
CreationTime = notification.CreationTime.ToBinary(),
Closed = true
}, cancellationToken);
}
catch (Exception ex)
{
this.logger.CreateScopedLogger().LogError(ex, "Failed to open notification");
return;
}
}
if (notification.HandlingType is null)
{
return;
}
var handler = this.serviceProvider.GetRequiredService<IEnumerable<INotificationHandler>>().FirstOrDefault(handler => handler.GetType() == notification.HandlingType)
?? throw new InvalidOperationException($"Could not find notification handler {notification.HandlingType}");
handler.OpenNotification(notification);
}
async Task INotificationProducer.RemoveNotification(Notification notification, CancellationToken cancellationToken)
{
await this.storage.RemoveNotification(ToDTO(notification), cancellationToken);
}
async Task INotificationProducer.RemoveAllNotifications(CancellationToken cancellationToken)
{
await this.storage.RemoveAllNotifications(cancellationToken);
}
async IAsyncEnumerable<Notification> INotificationProducer.Consume([EnumeratorCancellation] CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
while (this.pendingNotifications.TryDequeue(out var notification))
{
yield return notification;
}
await Task.Delay(1000, cancellationToken);
}
}
async ValueTask<IEnumerable<Notification>> INotificationProducer.GetAllNotifications(CancellationToken cancellationToken)
{
return (await this.storage.GetNotifications(cancellationToken)).Select(FromDTO);
}
async ValueTask<IEnumerable<Notification>> INotificationProducer.GetPendingNotifications(CancellationToken cancellationToken)
{
return (await this.storage.GetPendingNotifications(cancellationToken)).Select(FromDTO);
}
private NotificationToken NotifyInternal<THandlingType>(
string title,
string description,
string? metaData,
DateTime? expirationTime,
bool dismissible,
LogLevel logLevel,
bool persistent)
where THandlingType : class, INotificationHandler
{
var notification = new Notification<THandlingType>
{
Title = title,
Description = description,
Metadata = metaData ?? string.Empty,
ExpirationTime = expirationTime ?? (DateTime.UtcNow + TimeSpan.FromSeconds(5)),
Dismissible = dismissible,
Level = logLevel,
};
this.EnqueueNotification(notification, persistent);
return new NotificationToken(notification);
}
private async void EnqueueNotification(Notification notification, bool persistent)
{
if (persistent)
{
await this.storage.StoreNotification(ToDTO(notification), CancellationToken.None);
}
this.pendingNotifications.Enqueue(notification);
}
private static Notification FromDTO(NotificationDTO dto)
{
var handlingType = dto.HandlerType!.IsNullOrWhiteSpace() ?
default :
Type.GetType(dto.HandlerType!);
return new Notification
{
Id = dto.Id,
Level = (LogLevel)dto.Level,
Title = dto.Title ?? string.Empty,
Description = dto.Description ?? string.Empty,
ExpirationTime = DateTime.FromBinary(dto.ExpirationTime),
CreationTime = DateTime.FromBinary(dto.CreationTime),
Metadata = dto.MetaData ?? string.Empty,
Dismissible = dto.Dismissible,
Closed = dto.Closed,
HandlingType = handlingType ?? typeof(NoActionHandler),
};
}
private static NotificationDTO ToDTO(Notification notification)
{
return new NotificationDTO
{
Id = notification.Id,
Level = (int)notification.Level,
Title = notification.Title,
Description = notification.Description,
ExpirationTime = notification.ExpirationTime.ToBinary(),
CreationTime = notification.CreationTime.ToBinary(),
MetaData = notification.Metadata,
Dismissible = notification.Dismissible,
Closed = notification.Closed,
HandlerType = notification.HandlingType?.AssemblyQualifiedName
};
}
}