using System ;
using System.Collections.Generic ;
using System.Globalization ;
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
using Jellyfin.Data.Events ;
using MediaBrowser.Common.Configuration ;
using MediaBrowser.Controller ;
using MediaBrowser.Controller.Entities ;
using MediaBrowser.Controller.Entities.Audio ;
using MediaBrowser.Controller.Entities.TV ;
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.Notifications ;
using MediaBrowser.Controller.Plugins ;
using MediaBrowser.Model.Activity ;
using MediaBrowser.Model.Globalization ;
using MediaBrowser.Model.Notifications ;
using Microsoft.Extensions.Logging ;
namespace Emby.Notifications
{
/// <summary>
/// Creates notifications for various system events.
/// </summary>
public class NotificationEntryPoint : IServerEntryPoint
{
private readonly ILogger < NotificationEntryPoint > _logger ;
private readonly IActivityManager _activityManager ;
private readonly ILocalizationManager _localization ;
private readonly INotificationManager _notificationManager ;
private readonly ILibraryManager _libraryManager ;
private readonly IServerApplicationHost _appHost ;
private readonly IConfigurationManager _config ;
private readonly object _libraryChangedSyncLock = new object ( ) ;
private readonly List < BaseItem > _itemsAdded = new List < BaseItem > ( ) ;
private Timer ? _libraryUpdateTimer ;
private string [ ] _coreNotificationTypes ;
private bool _disposed = false ;
/// <summary>
/// Initializes a new instance of the <see cref="NotificationEntryPoint" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="activityManager">The activity manager.</param>
/// <param name="localization">The localization manager.</param>
/// <param name="notificationManager">The notification manager.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="appHost">The application host.</param>
/// <param name="config">The configuration manager.</param>
public NotificationEntryPoint (
ILogger < NotificationEntryPoint > logger ,
IActivityManager activityManager ,
ILocalizationManager localization ,
INotificationManager notificationManager ,
ILibraryManager libraryManager ,
IServerApplicationHost appHost ,
IConfigurationManager config )
{
_logger = logger ;
_activityManager = activityManager ;
_localization = localization ;
_notificationManager = notificationManager ;
_libraryManager = libraryManager ;
_appHost = appHost ;
_config = config ;
_coreNotificationTypes = new CoreNotificationTypes ( localization ) . GetNotificationTypes ( ) . Select ( i = > i . Type ) . ToArray ( ) ;
}
/// <inheritdoc />
public Task RunAsync ( )
{
_libraryManager . ItemAdded + = OnLibraryManagerItemAdded ;
_appHost . HasPendingRestartChanged + = OnAppHostHasPendingRestartChanged ;
_activityManager . EntryCreated + = OnActivityManagerEntryCreated ;
return Task . CompletedTask ;
}
private async void OnAppHostHasPendingRestartChanged ( object? sender , EventArgs e )
{
var type = NotificationType . ServerRestartRequired . ToString ( ) ;
var notification = new NotificationRequest
{
NotificationType = type ,
Name = string . Format (
CultureInfo . InvariantCulture ,
_localization . GetLocalizedString ( "ServerNameNeedsToBeRestarted" ) ,
_appHost . Name )
} ;
await SendNotification ( notification , null ) . ConfigureAwait ( false ) ;
}
private async void OnActivityManagerEntryCreated ( object? sender , GenericEventArgs < ActivityLogEntry > e )
{
var entry = e . Argument ;
var type = entry . Type ;
if ( string . IsNullOrEmpty ( type ) | | ! _coreNotificationTypes . Contains ( type , StringComparer . OrdinalIgnoreCase ) )
{
return ;
}
var userId = e . Argument . UserId ;
if ( ! userId . Equals ( Guid . Empty ) & & ! GetOptions ( ) . IsEnabledToMonitorUser ( type , userId ) )
{
return ;
}
var notification = new NotificationRequest
{
NotificationType = type ,
Name = entry . Name ,
Description = entry . Overview
} ;
await SendNotification ( notification , null ) . ConfigureAwait ( false ) ;
}
private NotificationOptions GetOptions ( )
{
return _config . GetConfiguration < NotificationOptions > ( "notifications" ) ;
}
private void OnLibraryManagerItemAdded ( object? sender , ItemChangeEventArgs e )
{
if ( ! FilterItem ( e . Item ) )
{
return ;
}
lock ( _libraryChangedSyncLock )
{
if ( _libraryUpdateTimer = = null )
{
_libraryUpdateTimer = new Timer (
LibraryUpdateTimerCallback ,
null ,
5000 ,
Timeout . Infinite ) ;
}
else
{
_libraryUpdateTimer . Change ( 5000 , Timeout . Infinite ) ;
}
_itemsAdded . Add ( e . Item ) ;
}
}
private bool FilterItem ( BaseItem item )
{
if ( item . IsFolder )
{
return false ;
}
if ( ! item . HasPathProtocol )
{
return false ;
}
if ( item is IItemByName )
{
return false ;
}
return item . SourceType = = SourceType . Library ;
}
private async void LibraryUpdateTimerCallback ( object? state )
{
List < BaseItem > items ;
lock ( _libraryChangedSyncLock )
{
items = _itemsAdded . ToList ( ) ;
_itemsAdded . Clear ( ) ;
_libraryUpdateTimer ! . Dispose ( ) ; // Shouldn't be null as it just set off this callback
_libraryUpdateTimer = null ;
}
if ( items . Count > 10 )
{
items = items . GetRange ( 0 , 10 ) ;
}
foreach ( var item in items )
{
var notification = new NotificationRequest
{
NotificationType = NotificationType . NewLibraryContent . ToString ( ) ,
Name = string . Format (
CultureInfo . InvariantCulture ,
_localization . GetLocalizedString ( "ValueHasBeenAddedToLibrary" ) ,
GetItemName ( item ) ) ,
Description = item . Overview
} ;
await SendNotification ( notification , item ) . ConfigureAwait ( false ) ;
}
}
/// <summary>
/// Creates a human readable name for the item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>A human readable name for the item.</returns>
public static string GetItemName ( BaseItem item )
{
var name = item . Name ;
if ( item is Episode episode )
{
if ( episode . IndexNumber . HasValue )
{
name = string . Format (
CultureInfo . InvariantCulture ,
"Ep{0} - {1}" ,
episode . IndexNumber . Value ,
name ) ;
}
if ( episode . ParentIndexNumber . HasValue )
{
name = string . Format (
CultureInfo . InvariantCulture ,
"S{0}, {1}" ,
episode . ParentIndexNumber . Value ,
name ) ;
}
}
if ( item is IHasSeries hasSeries )
{
name = hasSeries . SeriesName + " - " + name ;
}
if ( item is IHasAlbumArtist hasAlbumArtist )
{
var artists = hasAlbumArtist . AlbumArtists ;
if ( artists . Count > 0 )
{
name = artists [ 0 ] + " - " + name ;
}
}
else if ( item is IHasArtist hasArtist )
{
var artists = hasArtist . Artists ;
if ( artists . Count > 0 )
{
name = artists [ 0 ] + " - " + name ;
}
}
return name ;
}
private async Task SendNotification ( NotificationRequest notification , BaseItem ? relatedItem )
{
try
{
await _notificationManager . SendNotification ( notification , relatedItem , CancellationToken . None ) . ConfigureAwait ( false ) ;
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error sending notification" ) ;
}
}
/// <inheritdoc />
public void Dispose ( )
{
Dispose ( true ) ;
GC . SuppressFinalize ( this ) ;
}
/// <summary>
/// Releases unmanaged and optionally managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose ( bool disposing )
{
if ( _disposed )
{
return ;
}
if ( disposing )
{
_libraryUpdateTimer ? . Dispose ( ) ;
}
_libraryUpdateTimer = null ;
_libraryManager . ItemAdded - = OnLibraryManagerItemAdded ;
_appHost . HasPendingRestartChanged - = OnAppHostHasPendingRestartChanged ;
_activityManager . EntryCreated - = OnActivityManagerEntryCreated ;
_disposed = true ;
}
}
}