diff options
97 files changed, 2046 insertions, 1082 deletions
diff --git a/Emby.Dlna/Eventing/EventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs index cc8e0ae06..b66e966df 100644 --- a/Emby.Dlna/Eventing/EventManager.cs +++ b/Emby.Dlna/Eventing/DlnaEventManager.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Dlna.Eventing { - public class EventManager : IEventManager + public class DlnaEventManager : IDlnaEventManager { private readonly ConcurrentDictionary<string, EventSubscription> _subscriptions = new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase); @@ -24,7 +24,7 @@ namespace Emby.Dlna.Eventing private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public EventManager(ILogger logger, IHttpClient httpClient) + public DlnaEventManager(ILogger logger, IHttpClient httpClient) { _httpClient = httpClient; _logger = logger; diff --git a/Emby.Dlna/IConnectionManager.cs b/Emby.Dlna/IConnectionManager.cs index 7b4a33a98..9f643a9e6 100644 --- a/Emby.Dlna/IConnectionManager.cs +++ b/Emby.Dlna/IConnectionManager.cs @@ -2,7 +2,7 @@ namespace Emby.Dlna { - public interface IConnectionManager : IEventManager, IUpnpService + public interface IConnectionManager : IDlnaEventManager, IUpnpService { } } diff --git a/Emby.Dlna/IContentDirectory.cs b/Emby.Dlna/IContentDirectory.cs index 83ef09c66..10f4d6386 100644 --- a/Emby.Dlna/IContentDirectory.cs +++ b/Emby.Dlna/IContentDirectory.cs @@ -2,7 +2,7 @@ namespace Emby.Dlna { - public interface IContentDirectory : IEventManager, IUpnpService + public interface IContentDirectory : IDlnaEventManager, IUpnpService { } } diff --git a/Emby.Dlna/IEventManager.cs b/Emby.Dlna/IDlnaEventManager.cs index de87b0709..33cf0896b 100644 --- a/Emby.Dlna/IEventManager.cs +++ b/Emby.Dlna/IDlnaEventManager.cs @@ -2,7 +2,7 @@ namespace Emby.Dlna { - public interface IEventManager + public interface IDlnaEventManager { /// <summary> /// Cancels the event subscription. diff --git a/Emby.Dlna/IMediaReceiverRegistrar.cs b/Emby.Dlna/IMediaReceiverRegistrar.cs index b0376b6a9..43e934b53 100644 --- a/Emby.Dlna/IMediaReceiverRegistrar.cs +++ b/Emby.Dlna/IMediaReceiverRegistrar.cs @@ -2,7 +2,7 @@ namespace Emby.Dlna { - public interface IMediaReceiverRegistrar : IEventManager, IUpnpService + public interface IMediaReceiverRegistrar : IDlnaEventManager, IUpnpService { } } diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index d9e10f459..328759c5b 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Emby.Dlna.Didl; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; @@ -18,7 +19,6 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Session; using Microsoft.AspNetCore.WebUtilities; diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index 54f4fee05..ff801f263 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -16,7 +17,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; diff --git a/Emby.Dlna/Service/BaseService.cs b/Emby.Dlna/Service/BaseService.cs index 3ad1ea9e0..40d069e7c 100644 --- a/Emby.Dlna/Service/BaseService.cs +++ b/Emby.Dlna/Service/BaseService.cs @@ -6,17 +6,17 @@ using Microsoft.Extensions.Logging; namespace Emby.Dlna.Service { - public class BaseService : IEventManager + public class BaseService : IDlnaEventManager { protected BaseService(ILogger<BaseService> logger, IHttpClient httpClient) { Logger = logger; HttpClient = httpClient; - EventManager = new EventManager(logger, HttpClient); + EventManager = new DlnaEventManager(logger, HttpClient); } - protected IEventManager EventManager { get; } + protected IDlnaEventManager EventManager { get; } protected IHttpClient HttpClient { get; } diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index 7c4c0f6d9..8c7d961f3 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -3,9 +3,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Events; using Rssdp; using Rssdp.Infrastructure; diff --git a/Emby.Notifications/NotificationEntryPoint.cs b/Emby.Notifications/NotificationEntryPoint.cs index b923fd26c..ded22d26c 100644 --- a/Emby.Notifications/NotificationEntryPoint.cs +++ b/Emby.Notifications/NotificationEntryPoint.cs @@ -4,6 +4,7 @@ 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; @@ -13,7 +14,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Notifications; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs deleted file mode 100644 index 84bec9201..000000000 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ /dev/null @@ -1,590 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Jellyfin.Data.Entities; -using MediaBrowser.Common.Plugins; -using MediaBrowser.Common.Updates; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.Subtitles; -using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Notifications; -using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Updates; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.Activity -{ - /// <summary> - /// Entry point for the activity logger. - /// </summary> - public sealed class ActivityLogEntryPoint : IServerEntryPoint - { - private readonly ILogger<ActivityLogEntryPoint> _logger; - private readonly IInstallationManager _installationManager; - private readonly ISessionManager _sessionManager; - private readonly ITaskManager _taskManager; - private readonly IActivityManager _activityManager; - private readonly ILocalizationManager _localization; - private readonly ISubtitleManager _subManager; - private readonly IUserManager _userManager; - - /// <summary> - /// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class. - /// </summary> - /// <param name="logger">The logger.</param> - /// <param name="sessionManager">The session manager.</param> - /// <param name="taskManager">The task manager.</param> - /// <param name="activityManager">The activity manager.</param> - /// <param name="localization">The localization manager.</param> - /// <param name="installationManager">The installation manager.</param> - /// <param name="subManager">The subtitle manager.</param> - /// <param name="userManager">The user manager.</param> - public ActivityLogEntryPoint( - ILogger<ActivityLogEntryPoint> logger, - ISessionManager sessionManager, - ITaskManager taskManager, - IActivityManager activityManager, - ILocalizationManager localization, - IInstallationManager installationManager, - ISubtitleManager subManager, - IUserManager userManager) - { - _logger = logger; - _sessionManager = sessionManager; - _taskManager = taskManager; - _activityManager = activityManager; - _localization = localization; - _installationManager = installationManager; - _subManager = subManager; - _userManager = userManager; - } - - /// <inheritdoc /> - public Task RunAsync() - { - _taskManager.TaskCompleted += OnTaskCompleted; - - _installationManager.PluginInstalled += OnPluginInstalled; - _installationManager.PluginUninstalled += OnPluginUninstalled; - _installationManager.PluginUpdated += OnPluginUpdated; - _installationManager.PackageInstallationFailed += OnPackageInstallationFailed; - - _sessionManager.SessionStarted += OnSessionStarted; - _sessionManager.AuthenticationFailed += OnAuthenticationFailed; - _sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded; - _sessionManager.SessionEnded += OnSessionEnded; - _sessionManager.PlaybackStart += OnPlaybackStart; - _sessionManager.PlaybackStopped += OnPlaybackStopped; - - _subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure; - - _userManager.OnUserCreated += OnUserCreated; - _userManager.OnUserPasswordChanged += OnUserPasswordChanged; - _userManager.OnUserDeleted += OnUserDeleted; - _userManager.OnUserLockedOut += OnUserLockedOut; - - return Task.CompletedTask; - } - - private async void OnUserLockedOut(object sender, GenericEventArgs<User> e) - { - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("UserLockedOutWithName"), - e.Argument.Username), - NotificationType.UserLockedOut.ToString(), - e.Argument.Id) - { - LogSeverity = LogLevel.Error - }).ConfigureAwait(false); - } - - private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) - { - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), - e.Provider, - Notifications.NotificationEntryPoint.GetItemName(e.Item)), - "SubtitleDownloadFailure", - Guid.Empty) - { - ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture), - ShortOverview = e.Exception.Message - }).ConfigureAwait(false); - } - - private async void OnPlaybackStopped(object sender, PlaybackStopEventArgs e) - { - var item = e.MediaInfo; - - if (item == null) - { - _logger.LogWarning("PlaybackStopped reported with null media info."); - return; - } - - if (e.Item != null && e.Item.IsThemeMedia) - { - // Don't report theme song or local trailer playback - return; - } - - if (e.Users.Count == 0) - { - return; - } - - var user = e.Users[0]; - - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), - user.Username, - GetItemName(item), - e.DeviceName), - GetPlaybackStoppedNotificationType(item.MediaType), - user.Id)) - .ConfigureAwait(false); - } - - private async void OnPlaybackStart(object sender, PlaybackProgressEventArgs e) - { - var item = e.MediaInfo; - - if (item == null) - { - _logger.LogWarning("PlaybackStart reported with null media info."); - return; - } - - if (e.Item != null && e.Item.IsThemeMedia) - { - // Don't report theme song or local trailer playback - return; - } - - if (e.Users.Count == 0) - { - return; - } - - var user = e.Users.First(); - - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("UserStartedPlayingItemWithValues"), - user.Username, - GetItemName(item), - e.DeviceName), - GetPlaybackNotificationType(item.MediaType), - user.Id)) - .ConfigureAwait(false); - } - - private static string GetItemName(BaseItemDto item) - { - var name = item.Name; - - if (!string.IsNullOrEmpty(item.SeriesName)) - { - name = item.SeriesName + " - " + name; - } - - if (item.Artists != null && item.Artists.Count > 0) - { - name = item.Artists[0] + " - " + name; - } - - return name; - } - - private static string GetPlaybackNotificationType(string mediaType) - { - if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) - { - return NotificationType.AudioPlayback.ToString(); - } - - if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) - { - return NotificationType.VideoPlayback.ToString(); - } - - return null; - } - - private static string GetPlaybackStoppedNotificationType(string mediaType) - { - if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) - { - return NotificationType.AudioPlaybackStopped.ToString(); - } - - if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) - { - return NotificationType.VideoPlaybackStopped.ToString(); - } - - return null; - } - - private async void OnSessionEnded(object sender, SessionEventArgs e) - { - var session = e.SessionInfo; - - if (string.IsNullOrEmpty(session.UserName)) - { - return; - } - - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("UserOfflineFromDevice"), - session.UserName, - session.DeviceName), - "SessionEnded", - session.UserId) - { - ShortOverview = string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("LabelIpAddressValue"), - session.RemoteEndPoint), - }).ConfigureAwait(false); - } - - private async void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e) - { - var user = e.Argument.User; - - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("AuthenticationSucceededWithUserName"), - user.Name), - "AuthenticationSucceeded", - user.Id) - { - ShortOverview = string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("LabelIpAddressValue"), - e.Argument.SessionInfo.RemoteEndPoint), - }).ConfigureAwait(false); - } - - private async void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e) - { - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("FailedLoginAttemptWithUserName"), - e.Argument.Username), - "AuthenticationFailed", - Guid.Empty) - { - LogSeverity = LogLevel.Error, - ShortOverview = string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("LabelIpAddressValue"), - e.Argument.RemoteEndPoint), - }).ConfigureAwait(false); - } - - private async void OnUserDeleted(object sender, GenericEventArgs<User> e) - { - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("UserDeletedWithName"), - e.Argument.Username), - "UserDeleted", - Guid.Empty)) - .ConfigureAwait(false); - } - - private async void OnUserPasswordChanged(object sender, GenericEventArgs<User> e) - { - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("UserPasswordChangedWithName"), - e.Argument.Username), - "UserPasswordChanged", - e.Argument.Id)) - .ConfigureAwait(false); - } - - private async void OnUserCreated(object sender, GenericEventArgs<User> e) - { - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("UserCreatedWithName"), - e.Argument.Username), - "UserCreated", - e.Argument.Id)) - .ConfigureAwait(false); - } - - private async void OnSessionStarted(object sender, SessionEventArgs e) - { - var session = e.SessionInfo; - - if (string.IsNullOrEmpty(session.UserName)) - { - return; - } - - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("UserOnlineFromDevice"), - session.UserName, - session.DeviceName), - "SessionStarted", - session.UserId) - { - ShortOverview = string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("LabelIpAddressValue"), - session.RemoteEndPoint) - }).ConfigureAwait(false); - } - - private async void OnPluginUpdated(object sender, InstallationInfo e) - { - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("PluginUpdatedWithName"), - e.Name), - NotificationType.PluginUpdateInstalled.ToString(), - Guid.Empty) - { - ShortOverview = string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("VersionNumber"), - e.Version), - Overview = e.Changelog - }).ConfigureAwait(false); - } - - private async void OnPluginUninstalled(object sender, IPlugin e) - { - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("PluginUninstalledWithName"), - e.Name), - NotificationType.PluginUninstalled.ToString(), - Guid.Empty)) - .ConfigureAwait(false); - } - - private async void OnPluginInstalled(object sender, InstallationInfo e) - { - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("PluginInstalledWithName"), - e.Name), - NotificationType.PluginInstalled.ToString(), - Guid.Empty) - { - ShortOverview = string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("VersionNumber"), - e.Version) - }).ConfigureAwait(false); - } - - private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e) - { - var installationInfo = e.InstallationInfo; - - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("NameInstallFailed"), - installationInfo.Name), - NotificationType.InstallationFailed.ToString(), - Guid.Empty) - { - ShortOverview = string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("VersionNumber"), - installationInfo.Version), - Overview = e.Exception.Message - }).ConfigureAwait(false); - } - - private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e) - { - var result = e.Result; - var task = e.Task; - - if (task.ScheduledTask is IConfigurableScheduledTask activityTask - && !activityTask.IsLogged) - { - return; - } - - var time = result.EndTimeUtc - result.StartTimeUtc; - var runningTime = string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("LabelRunningTimeValue"), - ToUserFriendlyString(time)); - - if (result.Status == TaskCompletionStatus.Failed) - { - var vals = new List<string>(); - - if (!string.IsNullOrEmpty(e.Result.ErrorMessage)) - { - vals.Add(e.Result.ErrorMessage); - } - - if (!string.IsNullOrEmpty(e.Result.LongErrorMessage)) - { - vals.Add(e.Result.LongErrorMessage); - } - - await CreateLogEntry(new ActivityLog( - string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), - NotificationType.TaskFailed.ToString(), - Guid.Empty) - { - LogSeverity = LogLevel.Error, - Overview = string.Join(Environment.NewLine, vals), - ShortOverview = runningTime - }).ConfigureAwait(false); - } - } - - private async Task CreateLogEntry(ActivityLog entry) - => await _activityManager.CreateAsync(entry).ConfigureAwait(false); - - /// <inheritdoc /> - public void Dispose() - { - _taskManager.TaskCompleted -= OnTaskCompleted; - - _installationManager.PluginInstalled -= OnPluginInstalled; - _installationManager.PluginUninstalled -= OnPluginUninstalled; - _installationManager.PluginUpdated -= OnPluginUpdated; - _installationManager.PackageInstallationFailed -= OnPackageInstallationFailed; - - _sessionManager.SessionStarted -= OnSessionStarted; - _sessionManager.AuthenticationFailed -= OnAuthenticationFailed; - _sessionManager.AuthenticationSucceeded -= OnAuthenticationSucceeded; - _sessionManager.SessionEnded -= OnSessionEnded; - - _sessionManager.PlaybackStart -= OnPlaybackStart; - _sessionManager.PlaybackStopped -= OnPlaybackStopped; - - _subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure; - - _userManager.OnUserCreated -= OnUserCreated; - _userManager.OnUserPasswordChanged -= OnUserPasswordChanged; - _userManager.OnUserDeleted -= OnUserDeleted; - _userManager.OnUserLockedOut -= OnUserLockedOut; - } - - /// <summary> - /// Constructs a user-friendly string for this TimeSpan instance. - /// </summary> - private static string ToUserFriendlyString(TimeSpan span) - { - const int DaysInYear = 365; - const int DaysInMonth = 30; - - // Get each non-zero value from TimeSpan component - var values = new List<string>(); - - // Number of years - int days = span.Days; - if (days >= DaysInYear) - { - int years = days / DaysInYear; - values.Add(CreateValueString(years, "year")); - days %= DaysInYear; - } - - // Number of months - if (days >= DaysInMonth) - { - int months = days / DaysInMonth; - values.Add(CreateValueString(months, "month")); - days = days % DaysInMonth; - } - - // Number of days - if (days >= 1) - { - values.Add(CreateValueString(days, "day")); - } - - // Number of hours - if (span.Hours >= 1) - { - values.Add(CreateValueString(span.Hours, "hour")); - } - - // Number of minutes - if (span.Minutes >= 1) - { - values.Add(CreateValueString(span.Minutes, "minute")); - } - - // Number of seconds (include when 0 if no other components included) - if (span.Seconds >= 1 || values.Count == 0) - { - values.Add(CreateValueString(span.Seconds, "second")); - } - - // Combine values into string - var builder = new StringBuilder(); - for (int i = 0; i < values.Count; i++) - { - if (builder.Length > 0) - { - builder.Append(i == values.Count - 1 ? " and " : ", "); - } - - builder.Append(values[i]); - } - - // Return result - return builder.ToString(); - } - - /// <summary> - /// Constructs a string description of a time-span value. - /// </summary> - /// <param name="value">The value of this item.</param> - /// <param name="description">The name of this item (singular form).</param> - private static string CreateValueString(int value, string description) - { - return string.Format( - CultureInfo.InvariantCulture, - "{0:#,##0} {1}", - value, - value == 1 ? description : string.Format(CultureInfo.InvariantCulture, "{0}s", description)); - } - } -} diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 99530bfbd..cb63cc85e 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -53,7 +53,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; -using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Collections; @@ -173,6 +172,8 @@ namespace Emby.Server.Implementations /// </summary> protected ILogger<ApplicationHost> Logger { get; } + protected IServiceCollection ServiceCollection { get; } + private IPlugin[] _plugins; /// <summary> @@ -238,9 +239,11 @@ namespace Emby.Server.Implementations ILoggerFactory loggerFactory, IStartupOptions options, IFileSystem fileSystem, - INetworkManager networkManager) + INetworkManager networkManager, + IServiceCollection serviceCollection) { _xmlSerializer = new MyXmlSerializer(); + ServiceCollection = serviceCollection; _networkManager = networkManager; networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets; @@ -464,7 +467,7 @@ namespace Emby.Server.Implementations } /// <inheritdoc/> - public void Init(IServiceCollection serviceCollection) + public void Init() { HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; @@ -493,7 +496,7 @@ namespace Emby.Server.Implementations DiscoverTypes(); - RegisterServices(serviceCollection); + RegisterServices(); } public Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next) @@ -502,139 +505,139 @@ namespace Emby.Server.Implementations /// <summary> /// Registers services/resources with the service collection that will be available via DI. /// </summary> - protected virtual void RegisterServices(IServiceCollection serviceCollection) + protected virtual void RegisterServices() { - serviceCollection.AddSingleton(_startupOptions); + ServiceCollection.AddSingleton(_startupOptions); - serviceCollection.AddMemoryCache(); + ServiceCollection.AddMemoryCache(); - serviceCollection.AddSingleton(ConfigurationManager); - serviceCollection.AddSingleton<IApplicationHost>(this); + ServiceCollection.AddSingleton(ConfigurationManager); + ServiceCollection.AddSingleton<IApplicationHost>(this); - serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths); + ServiceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths); - serviceCollection.AddSingleton<IJsonSerializer, JsonSerializer>(); + ServiceCollection.AddSingleton<IJsonSerializer, JsonSerializer>(); - serviceCollection.AddSingleton(_fileSystemManager); - serviceCollection.AddSingleton<TvdbClientManager>(); + ServiceCollection.AddSingleton(_fileSystemManager); + ServiceCollection.AddSingleton<TvdbClientManager>(); - serviceCollection.AddSingleton<IHttpClient, HttpClientManager.HttpClientManager>(); + ServiceCollection.AddSingleton<IHttpClient, HttpClientManager.HttpClientManager>(); - serviceCollection.AddSingleton(_networkManager); + ServiceCollection.AddSingleton(_networkManager); - serviceCollection.AddSingleton<IIsoManager, IsoManager>(); + ServiceCollection.AddSingleton<IIsoManager, IsoManager>(); - serviceCollection.AddSingleton<ITaskManager, TaskManager>(); + ServiceCollection.AddSingleton<ITaskManager, TaskManager>(); - serviceCollection.AddSingleton(_xmlSerializer); + ServiceCollection.AddSingleton(_xmlSerializer); - serviceCollection.AddSingleton<IStreamHelper, StreamHelper>(); + ServiceCollection.AddSingleton<IStreamHelper, StreamHelper>(); - serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>(); + ServiceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>(); - serviceCollection.AddSingleton<ISocketFactory, SocketFactory>(); + ServiceCollection.AddSingleton<ISocketFactory, SocketFactory>(); - serviceCollection.AddSingleton<IInstallationManager, InstallationManager>(); + ServiceCollection.AddSingleton<IInstallationManager, InstallationManager>(); - serviceCollection.AddSingleton<IZipClient, ZipClient>(); + ServiceCollection.AddSingleton<IZipClient, ZipClient>(); - serviceCollection.AddSingleton<IHttpResultFactory, HttpResultFactory>(); + ServiceCollection.AddSingleton<IHttpResultFactory, HttpResultFactory>(); - serviceCollection.AddSingleton<IServerApplicationHost>(this); - serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths); + ServiceCollection.AddSingleton<IServerApplicationHost>(this); + ServiceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths); - serviceCollection.AddSingleton(ServerConfigurationManager); + ServiceCollection.AddSingleton(ServerConfigurationManager); - serviceCollection.AddSingleton<ILocalizationManager, LocalizationManager>(); + ServiceCollection.AddSingleton<ILocalizationManager, LocalizationManager>(); - serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>(); + ServiceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>(); - serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>(); - serviceCollection.AddSingleton<IUserDataManager, UserDataManager>(); + ServiceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>(); + ServiceCollection.AddSingleton<IUserDataManager, UserDataManager>(); - serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>(); + ServiceCollection.AddSingleton<IItemRepository, SqliteItemRepository>(); - serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>(); + ServiceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>(); // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required - serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>)); + ServiceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>)); // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required - serviceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>)); - serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(); + ServiceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>)); + ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(); // TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required - serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>)); - serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>)); - serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>)); - serviceCollection.AddSingleton<ILibraryManager, LibraryManager>(); + ServiceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>)); + ServiceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>)); + ServiceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>)); + ServiceCollection.AddSingleton<ILibraryManager, LibraryManager>(); - serviceCollection.AddSingleton<IMusicManager, MusicManager>(); + ServiceCollection.AddSingleton<IMusicManager, MusicManager>(); - serviceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>(); + ServiceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>(); - serviceCollection.AddSingleton<ISearchEngine, SearchEngine>(); + ServiceCollection.AddSingleton<ISearchEngine, SearchEngine>(); - serviceCollection.AddSingleton<ServiceController>(); - serviceCollection.AddSingleton<IHttpServer, HttpListenerHost>(); + ServiceCollection.AddSingleton<ServiceController>(); + ServiceCollection.AddSingleton<IHttpServer, HttpListenerHost>(); - serviceCollection.AddSingleton<IImageProcessor, ImageProcessor>(); + ServiceCollection.AddSingleton<IImageProcessor, ImageProcessor>(); - serviceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>(); + ServiceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>(); - serviceCollection.AddSingleton<IDeviceManager, DeviceManager>(); + ServiceCollection.AddSingleton<IDeviceManager, DeviceManager>(); - serviceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>(); + ServiceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>(); - serviceCollection.AddSingleton<ISubtitleManager, SubtitleManager>(); + ServiceCollection.AddSingleton<ISubtitleManager, SubtitleManager>(); - serviceCollection.AddSingleton<IProviderManager, ProviderManager>(); + ServiceCollection.AddSingleton<IProviderManager, ProviderManager>(); // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required - serviceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>)); - serviceCollection.AddSingleton<IDtoService, DtoService>(); + ServiceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>)); + ServiceCollection.AddSingleton<IDtoService, DtoService>(); - serviceCollection.AddSingleton<IChannelManager, ChannelManager>(); + ServiceCollection.AddSingleton<IChannelManager, ChannelManager>(); - serviceCollection.AddSingleton<ISessionManager, SessionManager>(); + ServiceCollection.AddSingleton<ISessionManager, SessionManager>(); - serviceCollection.AddSingleton<IDlnaManager, DlnaManager>(); + ServiceCollection.AddSingleton<IDlnaManager, DlnaManager>(); - serviceCollection.AddSingleton<ICollectionManager, CollectionManager>(); + ServiceCollection.AddSingleton<ICollectionManager, CollectionManager>(); - serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>(); + ServiceCollection.AddSingleton<IPlaylistManager, PlaylistManager>(); - serviceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>(); + ServiceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>(); - serviceCollection.AddSingleton<LiveTvDtoService>(); - serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>(); + ServiceCollection.AddSingleton<LiveTvDtoService>(); + ServiceCollection.AddSingleton<ILiveTvManager, LiveTvManager>(); - serviceCollection.AddSingleton<IUserViewManager, UserViewManager>(); + ServiceCollection.AddSingleton<IUserViewManager, UserViewManager>(); - serviceCollection.AddSingleton<INotificationManager, NotificationManager>(); + ServiceCollection.AddSingleton<INotificationManager, NotificationManager>(); - serviceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>(); + ServiceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>(); - serviceCollection.AddSingleton<IChapterManager, ChapterManager>(); + ServiceCollection.AddSingleton<IChapterManager, ChapterManager>(); - serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>(); + ServiceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>(); - serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>(); - serviceCollection.AddSingleton<ISessionContext, SessionContext>(); + ServiceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>(); + ServiceCollection.AddSingleton<ISessionContext, SessionContext>(); - serviceCollection.AddSingleton<IAuthService, AuthService>(); + ServiceCollection.AddSingleton<IAuthService, AuthService>(); - serviceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>(); + ServiceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>(); - serviceCollection.AddSingleton<IResourceFileManager, ResourceFileManager>(); - serviceCollection.AddSingleton<EncodingHelper>(); + ServiceCollection.AddSingleton<IResourceFileManager, ResourceFileManager>(); + ServiceCollection.AddSingleton<EncodingHelper>(); - serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>(); + ServiceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>(); - serviceCollection.AddSingleton<TranscodingJobHelper>(); - serviceCollection.AddScoped<MediaInfoHelper>(); - serviceCollection.AddScoped<AudioHelper>(); - serviceCollection.AddScoped<DynamicHlsHelper>(); + ServiceCollection.AddSingleton<TranscodingJobHelper>(); + ServiceCollection.AddScoped<MediaInfoHelper>(); + ServiceCollection.AddScoped<AudioHelper>(); + ServiceCollection.AddScoped<DynamicHlsHelper>(); } /// <summary> @@ -834,6 +837,8 @@ namespace Emby.Server.Implementations { hasPluginConfiguration.SetStartupInfo(s => Directory.CreateDirectory(s)); } + + plugin.RegisterServices(ServiceCollection); } catch (Exception ex) { diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index a15295fca..f05a30a89 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -2,11 +2,11 @@ using System; using System.Globalization; using System.IO; using Emby.Server.Implementations.AppBase; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index cc4b407f5..f98c694c4 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -7,13 +7,13 @@ using System.IO; using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Devices; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 9fce49425..2e8cc76d2 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -7,11 +7,11 @@ using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Events; using Microsoft.Extensions.Logging; using Mono.Nat; diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 1deef7f72..c9d21d963 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -15,7 +16,6 @@ using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 632735910..44d2580d6 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Plugins; @@ -43,22 +44,22 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - private async void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) + private async void OnLiveTvManagerSeriesTimerCreated(object sender, GenericEventArgs<TimerEventInfo> e) { await SendMessage("SeriesTimerCreated", e.Argument).ConfigureAwait(false); } - private async void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) + private async void OnLiveTvManagerTimerCreated(object sender, GenericEventArgs<TimerEventInfo> e) { await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false); } - private async void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) + private async void OnLiveTvManagerSeriesTimerCancelled(object sender, GenericEventArgs<TimerEventInfo> e) { await SendMessage("SeriesTimerCancelled", e.Argument).ConfigureAwait(false); } - private async void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) + private async void OnLiveTvManagerTimerCancelled(object sender, GenericEventArgs<TimerEventInfo> e) { await SendMessage("TimerCancelled", e.Argument).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs deleted file mode 100644 index 826d4d8dc..000000000 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ /dev/null @@ -1,210 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Threading; -using System.Threading.Tasks; -using Jellyfin.Data.Entities; -using MediaBrowser.Common.Plugins; -using MediaBrowser.Common.Updates; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Updates; - -namespace Emby.Server.Implementations.EntryPoints -{ - /// <summary> - /// Class WebSocketEvents. - /// </summary> - public class ServerEventNotifier : IServerEntryPoint - { - /// <summary> - /// The user manager. - /// </summary> - private readonly IUserManager _userManager; - - /// <summary> - /// The installation manager. - /// </summary> - private readonly IInstallationManager _installationManager; - - /// <summary> - /// The kernel. - /// </summary> - private readonly IServerApplicationHost _appHost; - - /// <summary> - /// The task manager. - /// </summary> - private readonly ITaskManager _taskManager; - - private readonly ISessionManager _sessionManager; - - /// <summary> - /// Initializes a new instance of the <see cref="ServerEventNotifier"/> class. - /// </summary> - /// <param name="appHost">The application host.</param> - /// <param name="userManager">The user manager.</param> - /// <param name="installationManager">The installation manager.</param> - /// <param name="taskManager">The task manager.</param> - /// <param name="sessionManager">The session manager.</param> - public ServerEventNotifier( - IServerApplicationHost appHost, - IUserManager userManager, - IInstallationManager installationManager, - ITaskManager taskManager, - ISessionManager sessionManager) - { - _userManager = userManager; - _installationManager = installationManager; - _appHost = appHost; - _taskManager = taskManager; - _sessionManager = sessionManager; - } - - /// <inheritdoc /> - public Task RunAsync() - { - _userManager.OnUserDeleted += OnUserDeleted; - _userManager.OnUserUpdated += OnUserUpdated; - - _appHost.HasPendingRestartChanged += OnHasPendingRestartChanged; - - _installationManager.PluginUninstalled += OnPluginUninstalled; - _installationManager.PackageInstalling += OnPackageInstalling; - _installationManager.PackageInstallationCancelled += OnPackageInstallationCancelled; - _installationManager.PackageInstallationCompleted += OnPackageInstallationCompleted; - _installationManager.PackageInstallationFailed += OnPackageInstallationFailed; - - _taskManager.TaskCompleted += OnTaskCompleted; - - return Task.CompletedTask; - } - - private async void OnPackageInstalling(object sender, InstallationInfo e) - { - await SendMessageToAdminSessions("PackageInstalling", e).ConfigureAwait(false); - } - - private async void OnPackageInstallationCancelled(object sender, InstallationInfo e) - { - await SendMessageToAdminSessions("PackageInstallationCancelled", e).ConfigureAwait(false); - } - - private async void OnPackageInstallationCompleted(object sender, InstallationInfo e) - { - await SendMessageToAdminSessions("PackageInstallationCompleted", e).ConfigureAwait(false); - } - - private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e) - { - await SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo).ConfigureAwait(false); - } - - private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e) - { - await SendMessageToAdminSessions("ScheduledTaskEnded", e.Result).ConfigureAwait(false); - } - - /// <summary> - /// Installations the manager_ plugin uninstalled. - /// </summary> - /// <param name="sender">The sender.</param> - /// <param name="e">The e.</param> - private async void OnPluginUninstalled(object sender, IPlugin e) - { - await SendMessageToAdminSessions("PluginUninstalled", e).ConfigureAwait(false); - } - - /// <summary> - /// Handles the HasPendingRestartChanged event of the kernel control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> - private async void OnHasPendingRestartChanged(object sender, EventArgs e) - { - await _sessionManager.SendRestartRequiredNotification(CancellationToken.None).ConfigureAwait(false); - } - - /// <summary> - /// Users the manager_ user updated. - /// </summary> - /// <param name="sender">The sender.</param> - /// <param name="e">The e.</param> - private async void OnUserUpdated(object sender, GenericEventArgs<User> e) - { - var dto = _userManager.GetUserDto(e.Argument); - - await SendMessageToUserSession(e.Argument, "UserUpdated", dto).ConfigureAwait(false); - } - - /// <summary> - /// Users the manager_ user deleted. - /// </summary> - /// <param name="sender">The sender.</param> - /// <param name="e">The e.</param> - private async void OnUserDeleted(object sender, GenericEventArgs<User> e) - { - await SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)).ConfigureAwait(false); - } - - private async Task SendMessageToAdminSessions<T>(string name, T data) - { - try - { - await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception) - { - } - } - - private async Task SendMessageToUserSession<T>(User user, string name, T data) - { - try - { - await _sessionManager.SendMessageToUserSessions( - new List<Guid> { user.Id }, - name, - data, - CancellationToken.None).ConfigureAwait(false); - } - catch (Exception) - { - } - } - - /// <inheritdoc /> - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - _userManager.OnUserDeleted -= OnUserDeleted; - _userManager.OnUserUpdated -= OnUserUpdated; - - _installationManager.PluginUninstalled -= OnPluginUninstalled; - _installationManager.PackageInstalling -= OnPackageInstalling; - _installationManager.PackageInstallationCancelled -= OnPackageInstallationCancelled; - _installationManager.PackageInstallationCompleted -= OnPackageInstallationCompleted; - _installationManager.PackageInstallationFailed -= OnPackageInstallationFailed; - - _appHost.HasPendingRestartChanged -= OnHasPendingRestartChanged; - - _taskManager.TaskCompleted -= OnTaskCompleted; - } - } - } -} diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index dafdd5b7b..fe39bb4b2 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -12,13 +12,13 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Services; using Emby.Server.Implementations.SocketSharp; +using Jellyfin.Data.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 80e09f0a3..09c52d95b 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using System.Xml; using Emby.Server.Implementations.Library; using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -29,7 +30,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 285a59a24..dd479b7d1 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -5,8 +5,8 @@ using System.Collections.Concurrent; using System.Globalization; using System.Linq; using System.Threading; +using Jellyfin.Data.Events; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.Events; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 5ed6baeb9..a898a564f 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Emby.Server.Implementations.Library; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; @@ -24,7 +25,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 1ef083d04..bc01f9543 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -6,10 +6,10 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index 6d2b4ffc8..6f81bf49b 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -5,8 +5,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 862a7296c..dad414142 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; @@ -17,6 +18,8 @@ using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Session; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; @@ -24,7 +27,6 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; @@ -40,25 +42,16 @@ namespace Emby.Server.Implementations.Session /// </summary> public class SessionManager : ISessionManager, IDisposable { - /// <summary> - /// The user data repository. - /// </summary> private readonly IUserDataManager _userDataManager; - - /// <summary> - /// The logger. - /// </summary> private readonly ILogger<SessionManager> _logger; - + private readonly IEventManager _eventManager; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IMusicManager _musicManager; private readonly IDtoService _dtoService; private readonly IImageProcessor _imageProcessor; private readonly IMediaSourceManager _mediaSourceManager; - private readonly IServerApplicationHost _appHost; - private readonly IAuthenticationRepository _authRepo; private readonly IDeviceManager _deviceManager; @@ -75,6 +68,7 @@ namespace Emby.Server.Implementations.Session public SessionManager( ILogger<SessionManager> logger, + IEventManager eventManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IUserManager userManager, @@ -87,6 +81,7 @@ namespace Emby.Server.Implementations.Session IMediaSourceManager mediaSourceManager) { _logger = logger; + _eventManager = eventManager; _userDataManager = userDataManager; _libraryManager = libraryManager; _userManager = userManager; @@ -209,6 +204,8 @@ namespace Emby.Server.Implementations.Session } } + _eventManager.Publish(new SessionStartedEventArgs(info)); + EventHelper.QueueEventIfNotNull( SessionStarted, this, @@ -230,6 +227,8 @@ namespace Emby.Server.Implementations.Session }, _logger); + _eventManager.Publish(new SessionEndedEventArgs(info)); + info.Dispose(); } @@ -667,22 +666,26 @@ namespace Emby.Server.Implementations.Session } } + var eventArgs = new PlaybackProgressEventArgs + { + Item = libraryItem, + Users = users, + MediaSourceId = info.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + Session = session + }; + + await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false); + // Nothing to save here // Fire events to inform plugins EventHelper.QueueEventIfNotNull( PlaybackStart, this, - new PlaybackProgressEventArgs - { - Item = libraryItem, - Users = users, - MediaSourceId = info.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - Session = session - }, + eventArgs, _logger); StartIdleCheckTimer(); @@ -750,23 +753,25 @@ namespace Emby.Server.Implementations.Session } } - PlaybackProgress?.Invoke( - this, - new PlaybackProgressEventArgs - { - Item = libraryItem, - Users = users, - PlaybackPositionTicks = session.PlayState.PositionTicks, - MediaSourceId = session.PlayState.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - IsPaused = info.IsPaused, - PlaySessionId = info.PlaySessionId, - IsAutomated = isAutomated, - Session = session - }); + var eventArgs = new PlaybackProgressEventArgs + { + Item = libraryItem, + Users = users, + PlaybackPositionTicks = session.PlayState.PositionTicks, + MediaSourceId = session.PlayState.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + IsPaused = info.IsPaused, + PlaySessionId = info.PlaySessionId, + IsAutomated = isAutomated, + Session = session + }; + + await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false); + + PlaybackProgress?.Invoke(this, eventArgs); if (!isAutomated) { @@ -943,23 +948,23 @@ namespace Emby.Server.Implementations.Session } } - EventHelper.QueueEventIfNotNull( - PlaybackStopped, - this, - new PlaybackStopEventArgs - { - Item = libraryItem, - Users = users, - PlaybackPositionTicks = info.PositionTicks, - PlayedToCompletion = playedToCompletion, - MediaSourceId = info.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - Session = session - }, - _logger); + var eventArgs = new PlaybackStopEventArgs + { + Item = libraryItem, + Users = users, + PlaybackPositionTicks = info.PositionTicks, + PlayedToCompletion = playedToCompletion, + MediaSourceId = info.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + Session = session + }; + + await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false); + + EventHelper.QueueEventIfNotNull(PlaybackStopped, this, eventArgs, _logger); } private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed) diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 8bebd37dc..1da7a6473 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -4,9 +4,9 @@ using System.Linq; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Net; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs index bbaf39eb1..ee87d917b 100644 --- a/Jellyfin.Api/Controllers/DlnaServerController.cs +++ b/Jellyfin.Api/Controllers/DlnaServerController.cs @@ -229,7 +229,7 @@ namespace Jellyfin.Api.Controllers }); } - private EventSubscriptionResponse ProcessEventRequest(IEventManager eventManager) + private EventSubscriptionResponse ProcessEventRequest(IDlnaEventManager dlnaEventManager) { var subscriptionId = Request.Headers["SID"]; if (string.Equals(Request.Method, "subscribe", StringComparison.OrdinalIgnoreCase)) @@ -240,17 +240,17 @@ namespace Jellyfin.Api.Controllers if (string.IsNullOrEmpty(notificationType)) { - return eventManager.RenewEventSubscription( + return dlnaEventManager.RenewEventSubscription( subscriptionId, notificationType, timeoutString, callback); } - return eventManager.CreateEventSubscription(notificationType, timeoutString, callback); + return dlnaEventManager.CreateEventSubscription(notificationType, timeoutString, callback); } - return eventManager.CancelEventSubscription(subscriptionId); + return dlnaEventManager.CancelEventSubscription(subscriptionId); } } } diff --git a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs index 6395b8d62..849b3b709 100644 --- a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs @@ -1,8 +1,8 @@ using System; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Events; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.WebSocketListeners diff --git a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs index 12f815ff7..8a966c137 100644 --- a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; diff --git a/MediaBrowser.Model/Events/GenericEventArgs.cs b/Jellyfin.Data/Events/GenericEventArgs.cs index 44f60f811..7b9a5111e 100644 --- a/MediaBrowser.Model/Events/GenericEventArgs.cs +++ b/Jellyfin.Data/Events/GenericEventArgs.cs @@ -1,20 +1,14 @@ using System; -namespace MediaBrowser.Model.Events +namespace Jellyfin.Data.Events { /// <summary> /// Provides a generic EventArgs subclass that can hold any kind of object. /// </summary> - /// <typeparam name="T"></typeparam> + /// <typeparam name="T">The type of this event.</typeparam> public class GenericEventArgs<T> : EventArgs { /// <summary> - /// Gets or sets the argument. - /// </summary> - /// <value>The argument.</value> - public T Argument { get; set; } - - /// <summary> /// Initializes a new instance of the <see cref="GenericEventArgs{T}"/> class. /// </summary> /// <param name="arg">The argument.</param> @@ -22,5 +16,11 @@ namespace MediaBrowser.Model.Events { Argument = arg; } + + /// <summary> + /// Gets the argument. + /// </summary> + /// <value>The argument.</value> + public T Argument { get; } } } diff --git a/Jellyfin.Data/Events/System/PendingRestartEventArgs.cs b/Jellyfin.Data/Events/System/PendingRestartEventArgs.cs new file mode 100644 index 000000000..2aa40a946 --- /dev/null +++ b/Jellyfin.Data/Events/System/PendingRestartEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace Jellyfin.Data.Events.System +{ + /// <summary> + /// An event that occurs when there is a pending restart. + /// </summary> + public class PendingRestartEventArgs : EventArgs + { + } +} diff --git a/Jellyfin.Data/Events/Users/UserCreatedEventArgs.cs b/Jellyfin.Data/Events/Users/UserCreatedEventArgs.cs new file mode 100644 index 000000000..66f7c8d4f --- /dev/null +++ b/Jellyfin.Data/Events/Users/UserCreatedEventArgs.cs @@ -0,0 +1,18 @@ +using Jellyfin.Data.Entities; + +namespace Jellyfin.Data.Events.Users +{ + /// <summary> + /// An event that occurs when a user is created. + /// </summary> + public class UserCreatedEventArgs : GenericEventArgs<User> + { + /// <summary> + /// Initializes a new instance of the <see cref="UserCreatedEventArgs"/> class. + /// </summary> + /// <param name="arg">The user.</param> + public UserCreatedEventArgs(User arg) : base(arg) + { + } + } +} diff --git a/Jellyfin.Data/Events/Users/UserDeletedEventArgs.cs b/Jellyfin.Data/Events/Users/UserDeletedEventArgs.cs new file mode 100644 index 000000000..0b9493375 --- /dev/null +++ b/Jellyfin.Data/Events/Users/UserDeletedEventArgs.cs @@ -0,0 +1,18 @@ +using Jellyfin.Data.Entities; + +namespace Jellyfin.Data.Events.Users +{ + /// <summary> + /// An event that occurs when a user is deleted. + /// </summary> + public class UserDeletedEventArgs : GenericEventArgs<User> + { + /// <summary> + /// Initializes a new instance of the <see cref="UserDeletedEventArgs"/> class. + /// </summary> + /// <param name="arg">The user.</param> + public UserDeletedEventArgs(User arg) : base(arg) + { + } + } +} diff --git a/Jellyfin.Data/Events/Users/UserLockedOutEventArgs.cs b/Jellyfin.Data/Events/Users/UserLockedOutEventArgs.cs new file mode 100644 index 000000000..cca3726dc --- /dev/null +++ b/Jellyfin.Data/Events/Users/UserLockedOutEventArgs.cs @@ -0,0 +1,18 @@ +using Jellyfin.Data.Entities; + +namespace Jellyfin.Data.Events.Users +{ + /// <summary> + /// An event that occurs when a user is locked out. + /// </summary> + public class UserLockedOutEventArgs : GenericEventArgs<User> + { + /// <summary> + /// Initializes a new instance of the <see cref="UserLockedOutEventArgs"/> class. + /// </summary> + /// <param name="arg">The user.</param> + public UserLockedOutEventArgs(User arg) : base(arg) + { + } + } +} diff --git a/Jellyfin.Data/Events/Users/UserPasswordChangedEventArgs.cs b/Jellyfin.Data/Events/Users/UserPasswordChangedEventArgs.cs new file mode 100644 index 000000000..087ec9ab6 --- /dev/null +++ b/Jellyfin.Data/Events/Users/UserPasswordChangedEventArgs.cs @@ -0,0 +1,18 @@ +using Jellyfin.Data.Entities; + +namespace Jellyfin.Data.Events.Users +{ + /// <summary> + /// An event that occurs when a user's password has changed. + /// </summary> + public class UserPasswordChangedEventArgs : GenericEventArgs<User> + { + /// <summary> + /// Initializes a new instance of the <see cref="UserPasswordChangedEventArgs"/> class. + /// </summary> + /// <param name="arg">The user.</param> + public UserPasswordChangedEventArgs(User arg) : base(arg) + { + } + } +} diff --git a/Jellyfin.Data/Events/Users/UserUpdatedEventArgs.cs b/Jellyfin.Data/Events/Users/UserUpdatedEventArgs.cs new file mode 100644 index 000000000..2b4e49cdf --- /dev/null +++ b/Jellyfin.Data/Events/Users/UserUpdatedEventArgs.cs @@ -0,0 +1,18 @@ +using Jellyfin.Data.Entities; + +namespace Jellyfin.Data.Events.Users +{ + /// <summary> + /// An event that occurs when a user is updated. + /// </summary> + public class UserUpdatedEventArgs : GenericEventArgs<User> + { + /// <summary> + /// Initializes a new instance of the <see cref="UserUpdatedEventArgs"/> class. + /// </summary> + /// <param name="arg">The user.</param> + public UserUpdatedEventArgs(User arg) : base(arg) + { + } + } +} diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index 2deefbe81..09f2611e4 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -2,8 +2,8 @@ using System; using System.Linq; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; namespace Jellyfin.Server.Implementations.Activity diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Library/SubtitleDownloadFailureLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Library/SubtitleDownloadFailureLogger.cs new file mode 100644 index 000000000..449f27be2 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Library/SubtitleDownloadFailureLogger.cs @@ -0,0 +1,102 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Library +{ + /// <summary> + /// Creates an entry in the activity log whenever a subtitle download fails. + /// </summary> + public class SubtitleDownloadFailureLogger : IEventConsumer<SubtitleDownloadFailureEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="SubtitleDownloadFailureLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public SubtitleDownloadFailureLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(SubtitleDownloadFailureEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("SubtitleDownloadFailureFromForItem"), + eventArgs.Provider, + GetItemName(eventArgs.Item)), + "SubtitleDownloadFailure", + Guid.Empty) + { + ItemId = eventArgs.Item.Id.ToString("N", CultureInfo.InvariantCulture), + ShortOverview = eventArgs.Exception.Message + }).ConfigureAwait(false); + } + + private 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; + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationFailedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationFailedLogger.cs new file mode 100644 index 000000000..f899b4497 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationFailedLogger.cs @@ -0,0 +1,52 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Security +{ + /// <summary> + /// Creates an entry in the activity log when there is a failed login attempt. + /// </summary> + public class AuthenticationFailedLogger : IEventConsumer<GenericEventArgs<AuthenticationRequest>> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthenticationFailedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public AuthenticationFailedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(GenericEventArgs<AuthenticationRequest> eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("FailedLoginAttemptWithUserName"), + eventArgs.Argument.Username), + "AuthenticationFailed", + Guid.Empty) + { + LogSeverity = LogLevel.Error, + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("LabelIpAddressValue"), + eventArgs.Argument.RemoteEndPoint), + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs new file mode 100644 index 000000000..2f9f44ed6 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs @@ -0,0 +1,49 @@ +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Events; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Security +{ + /// <summary> + /// Creates an entry in the activity log when there is a successful login attempt. + /// </summary> + public class AuthenticationSucceededLogger : IEventConsumer<GenericEventArgs<AuthenticationResult>> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthenticationSucceededLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public AuthenticationSucceededLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(GenericEventArgs<AuthenticationResult> e) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("AuthenticationSucceededWithUserName"), + e.Argument.User.Name), + "AuthenticationSucceeded", + e.Argument.User.Id) + { + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("LabelIpAddressValue"), + e.Argument.SessionInfo.RemoteEndPoint), + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs new file mode 100644 index 000000000..ec4a76e7f --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs @@ -0,0 +1,104 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Session +{ + /// <summary> + /// Creates an entry in the activity log whenever a user starts playback. + /// </summary> + public class PlaybackStartLogger : IEventConsumer<PlaybackStartEventArgs> + { + private readonly ILogger<PlaybackStartLogger> _logger; + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PlaybackStartLogger"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public PlaybackStartLogger(ILogger<PlaybackStartLogger> logger, ILocalizationManager localizationManager, IActivityManager activityManager) + { + _logger = logger; + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PlaybackStartEventArgs eventArgs) + { + if (eventArgs.MediaInfo == null) + { + _logger.LogWarning("PlaybackStart reported with null media info."); + return; + } + + if (eventArgs.Item != null && eventArgs.Item.IsThemeMedia) + { + // Don't report theme song or local trailer playback + return; + } + + if (eventArgs.Users.Count == 0) + { + return; + } + + var user = eventArgs.Users[0]; + + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserStartedPlayingItemWithValues"), + user.Username, + GetItemName(eventArgs.MediaInfo), + eventArgs.DeviceName), + GetPlaybackNotificationType(eventArgs.MediaInfo.MediaType), + user.Id)) + .ConfigureAwait(false); + } + + private static string GetItemName(BaseItemDto item) + { + var name = item.Name; + + if (!string.IsNullOrEmpty(item.SeriesName)) + { + name = item.SeriesName + " - " + name; + } + + if (item.Artists != null && item.Artists.Count > 0) + { + name = item.Artists[0] + " - " + name; + } + + return name; + } + + private static string GetPlaybackNotificationType(string mediaType) + { + if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.AudioPlayback.ToString(); + } + + if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.VideoPlayback.ToString(); + } + + return null; + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs new file mode 100644 index 000000000..51a882c14 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs @@ -0,0 +1,106 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Session +{ + /// <summary> + /// Creates an activity log entry whenever a user stops playback. + /// </summary> + public class PlaybackStopLogger : IEventConsumer<PlaybackStopEventArgs> + { + private readonly ILogger<PlaybackStopLogger> _logger; + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PlaybackStopLogger"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public PlaybackStopLogger(ILogger<PlaybackStopLogger> logger, ILocalizationManager localizationManager, IActivityManager activityManager) + { + _logger = logger; + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PlaybackStopEventArgs eventArgs) + { + var item = eventArgs.MediaInfo; + + if (item == null) + { + _logger.LogWarning("PlaybackStopped reported with null media info."); + return; + } + + if (eventArgs.Item != null && eventArgs.Item.IsThemeMedia) + { + // Don't report theme song or local trailer playback + return; + } + + if (eventArgs.Users.Count == 0) + { + return; + } + + var user = eventArgs.Users[0]; + + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserStoppedPlayingItemWithValues"), + user.Username, + GetItemName(item), + eventArgs.DeviceName), + GetPlaybackStoppedNotificationType(item.MediaType), + user.Id)) + .ConfigureAwait(false); + } + + private static string GetItemName(BaseItemDto item) + { + var name = item.Name; + + if (!string.IsNullOrEmpty(item.SeriesName)) + { + name = item.SeriesName + " - " + name; + } + + if (item.Artists != null && item.Artists.Count > 0) + { + name = item.Artists[0] + " - " + name; + } + + return name; + } + + private static string GetPlaybackStoppedNotificationType(string mediaType) + { + if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.AudioPlaybackStopped.ToString(); + } + + if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.VideoPlaybackStopped.ToString(); + } + + return null; + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/SessionEndedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/SessionEndedLogger.cs new file mode 100644 index 000000000..cf20946ec --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/SessionEndedLogger.cs @@ -0,0 +1,54 @@ +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Session; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Session +{ + /// <summary> + /// Creates an entry in the activity log whenever a session ends. + /// </summary> + public class SessionEndedLogger : IEventConsumer<SessionEndedEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="SessionEndedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public SessionEndedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(SessionEndedEventArgs eventArgs) + { + if (string.IsNullOrEmpty(eventArgs.Argument.UserName)) + { + return; + } + + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserOfflineFromDevice"), + eventArgs.Argument.UserName, + eventArgs.Argument.DeviceName), + "SessionEnded", + eventArgs.Argument.UserId) + { + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("LabelIpAddressValue"), + eventArgs.Argument.RemoteEndPoint), + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/SessionStartedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/SessionStartedLogger.cs new file mode 100644 index 000000000..6a0f29b09 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/SessionStartedLogger.cs @@ -0,0 +1,54 @@ +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Session; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Session +{ + /// <summary> + /// Creates an entry in the activity log when a session is started. + /// </summary> + public class SessionStartedLogger : IEventConsumer<SessionStartedEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="SessionStartedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public SessionStartedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(SessionStartedEventArgs eventArgs) + { + if (string.IsNullOrEmpty(eventArgs.Argument.UserName)) + { + return; + } + + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserOnlineFromDevice"), + eventArgs.Argument.UserName, + eventArgs.Argument.DeviceName), + "SessionStarted", + eventArgs.Argument.UserId) + { + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("LabelIpAddressValue"), + eventArgs.Argument.RemoteEndPoint) + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/System/PendingRestartNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/System/PendingRestartNotifier.cs new file mode 100644 index 000000000..2fa38dd71 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/System/PendingRestartNotifier.cs @@ -0,0 +1,31 @@ +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Events.System; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.System +{ + /// <summary> + /// Notifies users when there is a pending restart. + /// </summary> + public class PendingRestartNotifier : IEventConsumer<PendingRestartEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PendingRestartNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public PendingRestartNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PendingRestartEventArgs eventArgs) + { + await _sessionManager.SendRestartRequiredNotification(CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs new file mode 100644 index 000000000..05201a346 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Events.Consumers.System +{ + /// <summary> + /// Creates an activity log entry whenever a task is completed. + /// </summary> + public class TaskCompletedLogger : IEventConsumer<TaskCompletionEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="TaskCompletedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public TaskCompletedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(TaskCompletionEventArgs e) + { + var result = e.Result; + var task = e.Task; + + if (task.ScheduledTask is IConfigurableScheduledTask activityTask + && !activityTask.IsLogged) + { + return; + } + + var time = result.EndTimeUtc - result.StartTimeUtc; + var runningTime = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("LabelRunningTimeValue"), + ToUserFriendlyString(time)); + + if (result.Status == TaskCompletionStatus.Failed) + { + var vals = new List<string>(); + + if (!string.IsNullOrEmpty(e.Result.ErrorMessage)) + { + vals.Add(e.Result.ErrorMessage); + } + + if (!string.IsNullOrEmpty(e.Result.LongErrorMessage)) + { + vals.Add(e.Result.LongErrorMessage); + } + + await _activityManager.CreateAsync(new ActivityLog( + string.Format(CultureInfo.InvariantCulture, _localizationManager.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), + NotificationType.TaskFailed.ToString(), + Guid.Empty) + { + LogSeverity = LogLevel.Error, + Overview = string.Join(Environment.NewLine, vals), + ShortOverview = runningTime + }).ConfigureAwait(false); + } + } + + private static string ToUserFriendlyString(TimeSpan span) + { + const int DaysInYear = 365; + const int DaysInMonth = 30; + + // Get each non-zero value from TimeSpan component + var values = new List<string>(); + + // Number of years + int days = span.Days; + if (days >= DaysInYear) + { + int years = days / DaysInYear; + values.Add(CreateValueString(years, "year")); + days %= DaysInYear; + } + + // Number of months + if (days >= DaysInMonth) + { + int months = days / DaysInMonth; + values.Add(CreateValueString(months, "month")); + days = days % DaysInMonth; + } + + // Number of days + if (days >= 1) + { + values.Add(CreateValueString(days, "day")); + } + + // Number of hours + if (span.Hours >= 1) + { + values.Add(CreateValueString(span.Hours, "hour")); + } + + // Number of minutes + if (span.Minutes >= 1) + { + values.Add(CreateValueString(span.Minutes, "minute")); + } + + // Number of seconds (include when 0 if no other components included) + if (span.Seconds >= 1 || values.Count == 0) + { + values.Add(CreateValueString(span.Seconds, "second")); + } + + // Combine values into string + var builder = new StringBuilder(); + for (int i = 0; i < values.Count; i++) + { + if (builder.Length > 0) + { + builder.Append(i == values.Count - 1 ? " and " : ", "); + } + + builder.Append(values[i]); + } + + // Return result + return builder.ToString(); + } + + /// <summary> + /// Constructs a string description of a time-span value. + /// </summary> + /// <param name="value">The value of this item.</param> + /// <param name="description">The name of this item (singular form).</param> + private static string CreateValueString(int value, string description) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0:#,##0} {1}", + value, + value == 1 ? description : string.Format(CultureInfo.InvariantCulture, "{0}s", description)); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs new file mode 100644 index 000000000..80ed56cd8 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs @@ -0,0 +1,31 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Tasks; + +namespace Jellyfin.Server.Implementations.Events.Consumers.System +{ + /// <summary> + /// Notifies admin users when a task is completed. + /// </summary> + public class TaskCompletedNotifier : IEventConsumer<TaskCompletionEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="TaskCompletedNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public TaskCompletedNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(TaskCompletionEventArgs eventArgs) + { + await _sessionManager.SendMessageToAdminSessions("ScheduledTaskEnded", eventArgs.Result, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs new file mode 100644 index 000000000..1c600683a --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs @@ -0,0 +1,31 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Controller.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Notifies admin users when a plugin installation is cancelled. + /// </summary> + public class PluginInstallationCancelledNotifier : IEventConsumer<PluginInstallationCancelledEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstallationCancelledNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public PluginInstallationCancelledNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PluginInstallationCancelledEventArgs eventArgs) + { + await _sessionManager.SendMessageToAdminSessions("PackageInstallationCancelled", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedLogger.cs new file mode 100644 index 000000000..d71c298c5 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedLogger.cs @@ -0,0 +1,51 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller.Events; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Creates an entry in the activity log when a package installation fails. + /// </summary> + public class PluginInstallationFailedLogger : IEventConsumer<InstallationFailedEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstallationFailedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public PluginInstallationFailedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(InstallationFailedEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("NameInstallFailed"), + eventArgs.InstallationInfo.Name), + NotificationType.InstallationFailed.ToString(), + Guid.Empty) + { + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("VersionNumber"), + eventArgs.InstallationInfo.Version), + Overview = eventArgs.Exception.Message + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs new file mode 100644 index 000000000..ea0c878d4 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs @@ -0,0 +1,31 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Notifies admin users when a plugin installation fails. + /// </summary> + public class PluginInstallationFailedNotifier : IEventConsumer<InstallationFailedEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstallationFailedNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public PluginInstallationFailedNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(InstallationFailedEventArgs eventArgs) + { + await _sessionManager.SendMessageToAdminSessions("PackageInstallationFailed", eventArgs.InstallationInfo, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledLogger.cs new file mode 100644 index 000000000..8837172db --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledLogger.cs @@ -0,0 +1,50 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Creates an entry in the activity log when a plugin is installed. + /// </summary> + public class PluginInstalledLogger : IEventConsumer<PluginInstalledEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstalledLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public PluginInstalledLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PluginInstalledEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("PluginInstalledWithName"), + eventArgs.Argument.Name), + NotificationType.PluginInstalled.ToString(), + Guid.Empty) + { + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("VersionNumber"), + eventArgs.Argument.Version) + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs new file mode 100644 index 000000000..3dda5a04c --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs @@ -0,0 +1,31 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Controller.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Notifies admin users when a plugin is installed. + /// </summary> + public class PluginInstalledNotifier : IEventConsumer<PluginInstalledEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstalledNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public PluginInstalledNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PluginInstalledEventArgs eventArgs) + { + await _sessionManager.SendMessageToAdminSessions("PackageInstallationCompleted", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs new file mode 100644 index 000000000..f691d11a7 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs @@ -0,0 +1,31 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Controller.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Notifies admin users when a plugin is being installed. + /// </summary> + public class PluginInstallingNotifier : IEventConsumer<PluginInstallingEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstallingNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public PluginInstallingNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PluginInstallingEventArgs eventArgs) + { + await _sessionManager.SendMessageToAdminSessions("PackageInstalling", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs new file mode 100644 index 000000000..91a30069e --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs @@ -0,0 +1,45 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Creates an entry in the activity log when a plugin is uninstalled. + /// </summary> + public class PluginUninstalledLogger : IEventConsumer<PluginUninstalledEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginUninstalledLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public PluginUninstalledLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PluginUninstalledEventArgs e) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("PluginUninstalledWithName"), + e.Argument.Name), + NotificationType.PluginUninstalled.ToString(), + Guid.Empty)) + .ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs new file mode 100644 index 000000000..709692f6b --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs @@ -0,0 +1,31 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Controller.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Notifies admin users when a plugin is uninstalled. + /// </summary> + public class PluginUninstalledNotifier : IEventConsumer<PluginUninstalledEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginUninstalledNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public PluginUninstalledNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PluginUninstalledEventArgs eventArgs) + { + await _sessionManager.SendMessageToAdminSessions("PluginUninstalled", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUpdatedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUpdatedLogger.cs new file mode 100644 index 000000000..9ce16f774 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUpdatedLogger.cs @@ -0,0 +1,51 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Updates +{ + /// <summary> + /// Creates an entry in the activity log when a plugin is updated. + /// </summary> + public class PluginUpdatedLogger : IEventConsumer<PluginUpdatedEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PluginUpdatedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public PluginUpdatedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(PluginUpdatedEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("PluginUpdatedWithName"), + eventArgs.Argument.Name), + NotificationType.PluginUpdateInstalled.ToString(), + Guid.Empty) + { + ShortOverview = string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("VersionNumber"), + eventArgs.Argument.Version), + Overview = eventArgs.Argument.Changelog + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserCreatedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserCreatedLogger.cs new file mode 100644 index 000000000..dc855cc36 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserCreatedLogger.cs @@ -0,0 +1,43 @@ +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Events.Users; +using MediaBrowser.Controller.Events; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Users +{ + /// <summary> + /// Creates an entry in the activity log when a user is created. + /// </summary> + public class UserCreatedLogger : IEventConsumer<UserCreatedEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserCreatedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public UserCreatedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(UserCreatedEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserCreatedWithName"), + eventArgs.Argument.Username), + "UserCreated", + eventArgs.Argument.Id)) + .ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedLogger.cs new file mode 100644 index 000000000..c68a62c81 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedLogger.cs @@ -0,0 +1,44 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Events.Users; +using MediaBrowser.Controller.Events; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Users +{ + /// <summary> + /// Adds an entry to the activity log when a user is deleted. + /// </summary> + public class UserDeletedLogger : IEventConsumer<UserDeletedEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserDeletedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public UserDeletedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(UserDeletedEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserDeletedWithName"), + eventArgs.Argument.Username), + "UserDeleted", + Guid.Empty)) + .ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs new file mode 100644 index 000000000..10367a939 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Events.Users; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Users +{ + /// <summary> + /// Notifies the user's sessions when a user is deleted. + /// </summary> + public class UserDeletedNotifier : IEventConsumer<UserDeletedEventArgs> + { + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserDeletedNotifier"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + public UserDeletedNotifier(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(UserDeletedEventArgs eventArgs) + { + await _sessionManager.SendMessageToUserSessions( + new List<Guid> { eventArgs.Argument.Id }, + "UserDeleted", + eventArgs.Argument.Id.ToString("N", CultureInfo.InvariantCulture), + CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserLockedOutLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserLockedOutLogger.cs new file mode 100644 index 000000000..a31f222ee --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserLockedOutLogger.cs @@ -0,0 +1,47 @@ +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Events.Users; +using MediaBrowser.Controller.Events; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Notifications; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Users +{ + /// <summary> + /// Creates an entry in the activity log when a user is locked out. + /// </summary> + public class UserLockedOutLogger : IEventConsumer<UserLockedOutEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserLockedOutLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public UserLockedOutLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(UserLockedOutEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserLockedOutWithName"), + eventArgs.Argument.Username), + NotificationType.UserLockedOut.ToString(), + eventArgs.Argument.Id) + { + LogSeverity = LogLevel.Error + }).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserPasswordChangedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserPasswordChangedLogger.cs new file mode 100644 index 000000000..dc8ecbf48 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserPasswordChangedLogger.cs @@ -0,0 +1,43 @@ +using System.Globalization; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Events.Users; +using MediaBrowser.Controller.Events; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Users +{ + /// <summary> + /// Creates an entry in the activity log when a user's password is changed. + /// </summary> + public class UserPasswordChangedLogger : IEventConsumer<UserPasswordChangedEventArgs> + { + private readonly ILocalizationManager _localizationManager; + private readonly IActivityManager _activityManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserPasswordChangedLogger"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="activityManager">The activity manager.</param> + public UserPasswordChangedLogger(ILocalizationManager localizationManager, IActivityManager activityManager) + { + _localizationManager = localizationManager; + _activityManager = activityManager; + } + + /// <inheritdoc /> + public async Task OnEvent(UserPasswordChangedEventArgs eventArgs) + { + await _activityManager.CreateAsync(new ActivityLog( + string.Format( + CultureInfo.InvariantCulture, + _localizationManager.GetLocalizedString("UserPasswordChangedWithName"), + eventArgs.Argument.Username), + "UserPasswordChanged", + eventArgs.Argument.Id)) + .ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs new file mode 100644 index 000000000..6081dd044 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Events.Users; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; + +namespace Jellyfin.Server.Implementations.Events.Consumers.Users +{ + /// <summary> + /// Notifies a user when their account has been updated. + /// </summary> + public class UserUpdatedNotifier : IEventConsumer<UserUpdatedEventArgs> + { + private readonly IUserManager _userManager; + private readonly ISessionManager _sessionManager; + + /// <summary> + /// Initializes a new instance of the <see cref="UserUpdatedNotifier"/> class. + /// </summary> + /// <param name="userManager">The user manager.</param> + /// <param name="sessionManager">The session manager.</param> + public UserUpdatedNotifier(IUserManager userManager, ISessionManager sessionManager) + { + _userManager = userManager; + _sessionManager = sessionManager; + } + + /// <inheritdoc /> + public async Task OnEvent(UserUpdatedEventArgs e) + { + await _sessionManager.SendMessageToUserSessions( + new List<Guid> { e.Argument.Id }, + "UserUpdated", + _userManager.GetUserDto(e.Argument), + CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/EventManager.cs b/Jellyfin.Server.Implementations/Events/EventManager.cs new file mode 100644 index 000000000..707002442 --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/EventManager.cs @@ -0,0 +1,60 @@ +using System; +using System.Threading.Tasks; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Events; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Events +{ + /// <summary> + /// Handles the firing of events. + /// </summary> + public class EventManager : IEventManager + { + private readonly ILogger<EventManager> _logger; + private readonly IServerApplicationHost _appHost; + + /// <summary> + /// Initializes a new instance of the <see cref="EventManager"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="appHost">The application host.</param> + public EventManager(ILogger<EventManager> logger, IServerApplicationHost appHost) + { + _logger = logger; + _appHost = appHost; + } + + /// <inheritdoc /> + public void Publish<T>(T eventArgs) + where T : EventArgs + { + Task.WaitAll(PublishInternal(eventArgs)); + } + + /// <inheritdoc /> + public async Task PublishAsync<T>(T eventArgs) + where T : EventArgs + { + await PublishInternal(eventArgs).ConfigureAwait(false); + } + + private async Task PublishInternal<T>(T eventArgs) + where T : EventArgs + { + using var scope = _appHost.ServiceProvider.CreateScope(); + foreach (var service in scope.ServiceProvider.GetServices<IEventConsumer<T>>()) + { + try + { + await service.OnEvent(eventArgs).ConfigureAwait(false); + } + catch (Exception e) + { + _logger.LogError(e, "Uncaught exception in EventConsumer {type}: ", service.GetType()); + } + } + } + } +} diff --git a/Jellyfin.Server.Implementations/Events/EventingServiceCollectionExtensions.cs b/Jellyfin.Server.Implementations/Events/EventingServiceCollectionExtensions.cs new file mode 100644 index 000000000..5d558189b --- /dev/null +++ b/Jellyfin.Server.Implementations/Events/EventingServiceCollectionExtensions.cs @@ -0,0 +1,72 @@ +using Jellyfin.Data.Events; +using Jellyfin.Data.Events.System; +using Jellyfin.Data.Events.Users; +using Jellyfin.Server.Implementations.Events.Consumers.Library; +using Jellyfin.Server.Implementations.Events.Consumers.Security; +using Jellyfin.Server.Implementations.Events.Consumers.Session; +using Jellyfin.Server.Implementations.Events.Consumers.System; +using Jellyfin.Server.Implementations.Events.Consumers.Updates; +using Jellyfin.Server.Implementations.Events.Consumers.Users; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Session; +using MediaBrowser.Controller.Events.Updates; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Jellyfin.Server.Implementations.Events +{ + /// <summary> + /// A class containing extensions to <see cref="IServiceCollection"/> for eventing. + /// </summary> + public static class EventingServiceCollectionExtensions + { + /// <summary> + /// Adds the event services to the service collection. + /// </summary> + /// <param name="collection">The service collection.</param> + public static void AddEventServices(this IServiceCollection collection) + { + // Library consumers + collection.AddScoped<IEventConsumer<SubtitleDownloadFailureEventArgs>, SubtitleDownloadFailureLogger>(); + + // Security consumers + collection.AddScoped<IEventConsumer<GenericEventArgs<AuthenticationRequest>>, AuthenticationFailedLogger>(); + collection.AddScoped<IEventConsumer<GenericEventArgs<AuthenticationResult>>, AuthenticationSucceededLogger>(); + + // Session consumers + collection.AddScoped<IEventConsumer<PlaybackStartEventArgs>, PlaybackStartLogger>(); + collection.AddScoped<IEventConsumer<PlaybackStopEventArgs>, PlaybackStopLogger>(); + collection.AddScoped<IEventConsumer<SessionEndedEventArgs>, SessionEndedLogger>(); + collection.AddScoped<IEventConsumer<SessionStartedEventArgs>, SessionStartedLogger>(); + + // System consumers + collection.AddScoped<IEventConsumer<PendingRestartEventArgs>, PendingRestartNotifier>(); + collection.AddScoped<IEventConsumer<TaskCompletionEventArgs>, TaskCompletedLogger>(); + collection.AddScoped<IEventConsumer<TaskCompletionEventArgs>, TaskCompletedNotifier>(); + + // Update consumers + collection.AddScoped<IEventConsumer<PluginInstallationCancelledEventArgs>, PluginInstallationCancelledNotifier>(); + collection.AddScoped<IEventConsumer<InstallationFailedEventArgs>, PluginInstallationFailedLogger>(); + collection.AddScoped<IEventConsumer<InstallationFailedEventArgs>, PluginInstallationFailedNotifier>(); + collection.AddScoped<IEventConsumer<PluginInstalledEventArgs>, PluginInstalledLogger>(); + collection.AddScoped<IEventConsumer<PluginInstalledEventArgs>, PluginInstalledNotifier>(); + collection.AddScoped<IEventConsumer<PluginInstallingEventArgs>, PluginInstallingNotifier>(); + collection.AddScoped<IEventConsumer<PluginUninstalledEventArgs>, PluginUninstalledLogger>(); + collection.AddScoped<IEventConsumer<PluginUninstalledEventArgs>, PluginUninstalledNotifier>(); + collection.AddScoped<IEventConsumer<PluginUpdatedEventArgs>, PluginUpdatedLogger>(); + + // User consumers + collection.AddScoped<IEventConsumer<UserCreatedEventArgs>, UserCreatedLogger>(); + collection.AddScoped<IEventConsumer<UserDeletedEventArgs>, UserDeletedLogger>(); + collection.AddScoped<IEventConsumer<UserDeletedEventArgs>, UserDeletedNotifier>(); + collection.AddScoped<IEventConsumer<UserLockedOutEventArgs>, UserLockedOutLogger>(); + collection.AddScoped<IEventConsumer<UserPasswordChangedEventArgs>, UserPasswordChangedLogger>(); + collection.AddScoped<IEventConsumer<UserUpdatedEventArgs>, UserUpdatedNotifier>(); + } + } +} diff --git a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs index 140853e52..1fb89c4a6 100644 --- a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs +++ b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs @@ -4,12 +4,12 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Events; namespace Jellyfin.Server.Implementations.Users { diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 11402ee05..3e8edeb44 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -10,18 +10,20 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; +using Jellyfin.Data.Events.Users; using MediaBrowser.Common; using MediaBrowser.Common.Cryptography; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Users; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -34,6 +36,7 @@ namespace Jellyfin.Server.Implementations.Users public class UserManager : IUserManager { private readonly JellyfinDbProvider _dbProvider; + private readonly IEventManager _eventManager; private readonly ICryptoProvider _cryptoProvider; private readonly INetworkManager _networkManager; private readonly IApplicationHost _appHost; @@ -49,6 +52,7 @@ namespace Jellyfin.Server.Implementations.Users /// Initializes a new instance of the <see cref="UserManager"/> class. /// </summary> /// <param name="dbProvider">The database provider.</param> + /// <param name="eventManager">The event manager.</param> /// <param name="cryptoProvider">The cryptography provider.</param> /// <param name="networkManager">The network manager.</param> /// <param name="appHost">The application host.</param> @@ -56,6 +60,7 @@ namespace Jellyfin.Server.Implementations.Users /// <param name="logger">The logger.</param> public UserManager( JellyfinDbProvider dbProvider, + IEventManager eventManager, ICryptoProvider cryptoProvider, INetworkManager networkManager, IApplicationHost appHost, @@ -63,6 +68,7 @@ namespace Jellyfin.Server.Implementations.Users ILogger<UserManager> logger) { _dbProvider = dbProvider; + _eventManager = eventManager; _cryptoProvider = cryptoProvider; _networkManager = networkManager; _appHost = appHost; @@ -78,21 +84,9 @@ namespace Jellyfin.Server.Implementations.Users } /// <inheritdoc/> - public event EventHandler<GenericEventArgs<User>>? OnUserPasswordChanged; - - /// <inheritdoc/> public event EventHandler<GenericEventArgs<User>>? OnUserUpdated; /// <inheritdoc/> - public event EventHandler<GenericEventArgs<User>>? OnUserCreated; - - /// <inheritdoc/> - public event EventHandler<GenericEventArgs<User>>? OnUserDeleted; - - /// <inheritdoc/> - public event EventHandler<GenericEventArgs<User>>? OnUserLockedOut; - - /// <inheritdoc/> public IEnumerable<User> Users { get @@ -234,7 +228,7 @@ namespace Jellyfin.Server.Implementations.Users dbContext.Users.Add(newUser); await dbContext.SaveChangesAsync().ConfigureAwait(false); - OnUserCreated?.Invoke(this, new GenericEventArgs<User>(newUser)); + await _eventManager.PublishAsync(new UserCreatedEventArgs(newUser)).ConfigureAwait(false); return newUser; } @@ -293,7 +287,8 @@ namespace Jellyfin.Server.Implementations.Users dbContext.RemoveRange(user.AccessSchedules); dbContext.Users.Remove(user); dbContext.SaveChanges(); - OnUserDeleted?.Invoke(this, new GenericEventArgs<User>(user)); + + _eventManager.Publish(new UserDeletedEventArgs(user)); } /// <inheritdoc/> @@ -319,7 +314,7 @@ namespace Jellyfin.Server.Implementations.Users await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); await UpdateUserAsync(user).ConfigureAwait(false); - OnUserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user)); + await _eventManager.PublishAsync(new UserPasswordChangedEventArgs(user)).ConfigureAwait(false); } /// <inheritdoc/> @@ -338,7 +333,7 @@ namespace Jellyfin.Server.Implementations.Users user.EasyPassword = newPasswordSha1; UpdateUser(user); - OnUserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user)); + _eventManager.Publish(new UserPasswordChangedEventArgs(user)); } /// <inheritdoc/> @@ -901,7 +896,7 @@ namespace Jellyfin.Server.Implementations.Users if (maxInvalidLogins.HasValue && user.InvalidLoginAttemptCount >= maxInvalidLogins) { user.SetPermission(PermissionKind.IsDisabled, true); - OnUserLockedOut?.Invoke(this, new GenericEventArgs<User>(user)); + await _eventManager.PublishAsync(new UserLockedOutEventArgs(user)).ConfigureAwait(false); _logger.LogWarning( "Disabling user {Username} due to {Attempts} unsuccessful login attempts.", user.Username, diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 29a59e1c8..755844dd9 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -1,20 +1,20 @@ using System; using System.Collections.Generic; -using System.IO; using System.Reflection; using Emby.Drawing; using Emby.Server.Implementations; using Jellyfin.Drawing.Skia; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; +using Jellyfin.Server.Implementations.Events; using Jellyfin.Server.Implementations.Users; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Activity; using MediaBrowser.Model.IO; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -33,30 +33,33 @@ namespace Jellyfin.Server /// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param> /// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param> /// <param name="networkManager">The <see cref="INetworkManager" /> to be used by the <see cref="CoreAppHost" />.</param> + /// <param name="collection">The <see cref="IServiceCollection"/> to be used by the <see cref="CoreAppHost"/>.</param> public CoreAppHost( IServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IStartupOptions options, IFileSystem fileSystem, - INetworkManager networkManager) + INetworkManager networkManager, + IServiceCollection collection) : base( applicationPaths, loggerFactory, options, fileSystem, - networkManager) + networkManager, + collection) { } /// <inheritdoc/> - protected override void RegisterServices(IServiceCollection serviceCollection) + protected override void RegisterServices() { // Register an image encoder bool useSkiaEncoder = SkiaEncoder.IsNativeLibAvailable(); Type imageEncoderType = useSkiaEncoder ? typeof(SkiaEncoder) : typeof(NullImageEncoder); - serviceCollection.AddSingleton(typeof(IImageEncoder), imageEncoderType); + ServiceCollection.AddSingleton(typeof(IImageEncoder), imageEncoderType); // Log a warning if the Skia encoder could not be used if (!useSkiaEncoder) @@ -71,13 +74,15 @@ namespace Jellyfin.Server // .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"), // ServiceLifetime.Transient); - serviceCollection.AddSingleton<JellyfinDbProvider>(); + ServiceCollection.AddEventServices(); + ServiceCollection.AddSingleton<IEventManager, EventManager>(); + ServiceCollection.AddSingleton<JellyfinDbProvider>(); - serviceCollection.AddSingleton<IActivityManager, ActivityManager>(); - serviceCollection.AddSingleton<IUserManager, UserManager>(); - serviceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>(); + ServiceCollection.AddSingleton<IActivityManager, ActivityManager>(); + ServiceCollection.AddSingleton<IUserManager, UserManager>(); + ServiceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>(); - base.RegisterServices(serviceCollection); + base.RegisterServices(); } /// <inheritdoc /> diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index f6ac4e2a3..14cc5f4c2 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -154,13 +154,15 @@ namespace Jellyfin.Server ApplicationHost.LogEnvironmentInfo(_logger, appPaths); PerformStaticInitialization(); + var serviceCollection = new ServiceCollection(); var appHost = new CoreAppHost( appPaths, _loggerFactory, options, new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths), - new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>())); + new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()), + serviceCollection); try { @@ -178,8 +180,7 @@ namespace Jellyfin.Server } } - ServiceCollection serviceCollection = new ServiceCollection(); - appHost.Init(serviceCollection); + appHost.Init(); var webHost = new WebHostBuilder().ConfigureWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build(); diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index e8d9282e4..7a76be722 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -116,8 +116,7 @@ namespace MediaBrowser.Common /// <summary> /// Initializes this instance. /// </summary> - /// <param name="serviceCollection">The service collection.</param> - void Init(IServiceCollection serviceCollection); + void Init(); /// <summary> /// Creates the instance. diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index f10a1918f..4b2918d08 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -6,6 +6,7 @@ using System.Reflection; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.DependencyInjection; namespace MediaBrowser.Common.Plugins { @@ -82,6 +83,16 @@ namespace MediaBrowser.Common.Plugins } /// <inheritdoc /> + public virtual void RegisterServices(IServiceCollection serviceCollection) + { + } + + /// <inheritdoc /> + public virtual void UnregisterServices(IServiceCollection serviceCollection) + { + } + + /// <inheritdoc /> public void SetAttributes(string assemblyFilePath, string dataFolderPath, Version assemblyVersion) { AssemblyFilePath = assemblyFilePath; diff --git a/MediaBrowser.Common/Plugins/IPlugin.cs b/MediaBrowser.Common/Plugins/IPlugin.cs index 7bd37d210..1844eb124 100644 --- a/MediaBrowser.Common/Plugins/IPlugin.cs +++ b/MediaBrowser.Common/Plugins/IPlugin.cs @@ -2,6 +2,7 @@ using System; using MediaBrowser.Model.Plugins; +using Microsoft.Extensions.DependencyInjection; namespace MediaBrowser.Common.Plugins { @@ -61,6 +62,18 @@ namespace MediaBrowser.Common.Plugins /// Called when just before the plugin is uninstalled from the server. /// </summary> void OnUninstalling(); + + /// <summary> + /// Registers the plugin's services to the service collection. + /// </summary> + /// <param name="serviceCollection">The service collection.</param> + void RegisterServices(IServiceCollection serviceCollection); + + /// <summary> + /// Unregisters the plugin's services from the service collection. + /// </summary> + /// <param name="serviceCollection">The service collection.</param> + void UnregisterServices(IServiceCollection serviceCollection); } public interface IHasPluginConfiguration diff --git a/MediaBrowser.Common/Updates/InstallationEventArgs.cs b/MediaBrowser.Common/Updates/InstallationEventArgs.cs index 11eb2ad34..61178f631 100644 --- a/MediaBrowser.Common/Updates/InstallationEventArgs.cs +++ b/MediaBrowser.Common/Updates/InstallationEventArgs.cs @@ -1,10 +1,11 @@ #pragma warning disable CS1591 +using System; using MediaBrowser.Model.Updates; namespace MediaBrowser.Common.Updates { - public class InstallationEventArgs + public class InstallationEventArgs : EventArgs { public InstallationInfo InstallationInfo { get; set; } diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 55e022f15..8f0872dba 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -2,8 +2,8 @@ using System; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Model.Devices; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; diff --git a/MediaBrowser.Controller/Events/IEventConsumer.cs b/MediaBrowser.Controller/Events/IEventConsumer.cs new file mode 100644 index 000000000..3cefe2f9c --- /dev/null +++ b/MediaBrowser.Controller/Events/IEventConsumer.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Events +{ + public interface IEventConsumer<in T> + where T : EventArgs + { + Task OnEvent(T eventArgs); + } +} diff --git a/MediaBrowser.Controller/Events/IEventManager.cs b/MediaBrowser.Controller/Events/IEventManager.cs new file mode 100644 index 000000000..a1f40b3a6 --- /dev/null +++ b/MediaBrowser.Controller/Events/IEventManager.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Events +{ + /// <summary> + /// An interface that handles eventing. + /// </summary> + public interface IEventManager + { + /// <summary> + /// Publishes an event. + /// </summary> + /// <param name="eventArgs">the event arguments.</param> + /// <typeparam name="T">The type of event.</typeparam> + void Publish<T>(T eventArgs) + where T : EventArgs; + + /// <summary> + /// Publishes an event asynchronously. + /// </summary> + /// <param name="eventArgs">The event arguments.</param> + /// <typeparam name="T">The type of event.</typeparam> + /// <returns>A task representing the publishing of the event.</returns> + Task PublishAsync<T>(T eventArgs) + where T : EventArgs; + } +} diff --git a/MediaBrowser.Controller/Events/Session/SessionEndedEventArgs.cs b/MediaBrowser.Controller/Events/Session/SessionEndedEventArgs.cs new file mode 100644 index 000000000..46d7e5a17 --- /dev/null +++ b/MediaBrowser.Controller/Events/Session/SessionEndedEventArgs.cs @@ -0,0 +1,19 @@ +using Jellyfin.Data.Events; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.Events.Session +{ + /// <summary> + /// An event that fires when a session is ended. + /// </summary> + public class SessionEndedEventArgs : GenericEventArgs<SessionInfo> + { + /// <summary> + /// Initializes a new instance of the <see cref="SessionEndedEventArgs"/> class. + /// </summary> + /// <param name="arg">The session info.</param> + public SessionEndedEventArgs(SessionInfo arg) : base(arg) + { + } + } +} diff --git a/MediaBrowser.Controller/Events/Session/SessionStartedEventArgs.cs b/MediaBrowser.Controller/Events/Session/SessionStartedEventArgs.cs new file mode 100644 index 000000000..aab19cc46 --- /dev/null +++ b/MediaBrowser.Controller/Events/Session/SessionStartedEventArgs.cs @@ -0,0 +1,19 @@ +using Jellyfin.Data.Events; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.Events.Session +{ + /// <summary> + /// An event that fires when a session is started. + /// </summary> + public class SessionStartedEventArgs : GenericEventArgs<SessionInfo> + { + /// <summary> + /// Initializes a new instance of the <see cref="SessionStartedEventArgs"/> class. + /// </summary> + /// <param name="arg">The session info.</param> + public SessionStartedEventArgs(SessionInfo arg) : base(arg) + { + } + } +} diff --git a/MediaBrowser.Controller/Events/Updates/PluginInstallationCancelledEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginInstallationCancelledEventArgs.cs new file mode 100644 index 000000000..b06046c05 --- /dev/null +++ b/MediaBrowser.Controller/Events/Updates/PluginInstallationCancelledEventArgs.cs @@ -0,0 +1,19 @@ +using Jellyfin.Data.Events; +using MediaBrowser.Model.Updates; + +namespace MediaBrowser.Controller.Events.Updates +{ + /// <summary> + /// An event that occurs when a plugin installation is cancelled. + /// </summary> + public class PluginInstallationCancelledEventArgs : GenericEventArgs<InstallationInfo> + { + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstallationCancelledEventArgs"/> class. + /// </summary> + /// <param name="arg">The installation info.</param> + public PluginInstallationCancelledEventArgs(InstallationInfo arg) : base(arg) + { + } + } +} diff --git a/MediaBrowser.Controller/Events/Updates/PluginInstalledEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginInstalledEventArgs.cs new file mode 100644 index 000000000..dfadc9f61 --- /dev/null +++ b/MediaBrowser.Controller/Events/Updates/PluginInstalledEventArgs.cs @@ -0,0 +1,19 @@ +using Jellyfin.Data.Events; +using MediaBrowser.Model.Updates; + +namespace MediaBrowser.Controller.Events.Updates +{ + /// <summary> + /// An event that occurs when a plugin is installed. + /// </summary> + public class PluginInstalledEventArgs : GenericEventArgs<InstallationInfo> + { + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstalledEventArgs"/> class. + /// </summary> + /// <param name="arg">The installation info.</param> + public PluginInstalledEventArgs(InstallationInfo arg) : base(arg) + { + } + } +} diff --git a/MediaBrowser.Controller/Events/Updates/PluginInstallingEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginInstallingEventArgs.cs new file mode 100644 index 000000000..045a60027 --- /dev/null +++ b/MediaBrowser.Controller/Events/Updates/PluginInstallingEventArgs.cs @@ -0,0 +1,19 @@ +using Jellyfin.Data.Events; +using MediaBrowser.Model.Updates; + +namespace MediaBrowser.Controller.Events.Updates +{ + /// <summary> + /// An event that occurs when a plugin is installing. + /// </summary> + public class PluginInstallingEventArgs : GenericEventArgs<InstallationInfo> + { + /// <summary> + /// Initializes a new instance of the <see cref="PluginInstallingEventArgs"/> class. + /// </summary> + /// <param name="arg">The installation info.</param> + public PluginInstallingEventArgs(InstallationInfo arg) : base(arg) + { + } + } +} diff --git a/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs new file mode 100644 index 000000000..7510b62b8 --- /dev/null +++ b/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs @@ -0,0 +1,19 @@ +using Jellyfin.Data.Events; +using MediaBrowser.Common.Plugins; + +namespace MediaBrowser.Controller.Events.Updates +{ + /// <summary> + /// An event that occurs when a plugin is uninstalled. + /// </summary> + public class PluginUninstalledEventArgs : GenericEventArgs<IPlugin> + { + /// <summary> + /// Initializes a new instance of the <see cref="PluginUninstalledEventArgs"/> class. + /// </summary> + /// <param name="arg">The plugin.</param> + public PluginUninstalledEventArgs(IPlugin arg) : base(arg) + { + } + } +} diff --git a/MediaBrowser.Controller/Events/Updates/PluginUpdatedEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginUpdatedEventArgs.cs new file mode 100644 index 000000000..661ca066a --- /dev/null +++ b/MediaBrowser.Controller/Events/Updates/PluginUpdatedEventArgs.cs @@ -0,0 +1,19 @@ +using Jellyfin.Data.Events; +using MediaBrowser.Model.Updates; + +namespace MediaBrowser.Controller.Events.Updates +{ + /// <summary> + /// An event that occurs when a plugin is updated. + /// </summary> + public class PluginUpdatedEventArgs : GenericEventArgs<InstallationInfo> + { + /// <summary> + /// Initializes a new instance of the <see cref="PluginUpdatedEventArgs"/> class. + /// </summary> + /// <param name="arg">The installation info.</param> + public PluginUpdatedEventArgs(InstallationInfo arg) : base(arg) + { + } + } +} diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 2db631157..39b896c0f 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -18,13 +18,7 @@ namespace MediaBrowser.Controller { event EventHandler HasUpdateAvailableChanged; - /// <summary> - /// Gets the system info. - /// </summary> - /// <returns>SystemInfo.</returns> - Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken); - - Task<PublicSystemInfo> GetPublicSystemInfo(CancellationToken cancellationToken); + IServiceProvider ServiceProvider { get; } bool CanLaunchWebBrowser { get; } @@ -58,6 +52,14 @@ namespace MediaBrowser.Controller string FriendlyName { get; } /// <summary> + /// Gets the system info. + /// </summary> + /// <returns>SystemInfo.</returns> + Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken); + + Task<PublicSystemInfo> GetPublicSystemInfo(CancellationToken cancellationToken); + + /// <summary> /// Gets all the local IP addresses of this API instance. Each address is validated by sending a 'ping' request /// to the API that should exist at the address. /// </summary> diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index ffd9739f6..6a4f5cf67 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Library @@ -22,26 +22,6 @@ namespace MediaBrowser.Controller.Library event EventHandler<GenericEventArgs<User>> OnUserUpdated; /// <summary> - /// Occurs when a user is created. - /// </summary> - event EventHandler<GenericEventArgs<User>> OnUserCreated; - - /// <summary> - /// Occurs when a user is deleted. - /// </summary> - event EventHandler<GenericEventArgs<User>> OnUserDeleted; - - /// <summary> - /// Occurs when a user's password is changed. - /// </summary> - event EventHandler<GenericEventArgs<User>> OnUserPasswordChanged; - - /// <summary> - /// Occurs when a user is locked out. - /// </summary> - event EventHandler<GenericEventArgs<User>> OnUserLockedOut; - - /// <summary> /// Gets the users. /// </summary> /// <value>The users.</value> diff --git a/MediaBrowser.Controller/Library/PlaybackStartEventArgs.cs b/MediaBrowser.Controller/Library/PlaybackStartEventArgs.cs new file mode 100644 index 000000000..3aa9c2895 --- /dev/null +++ b/MediaBrowser.Controller/Library/PlaybackStartEventArgs.cs @@ -0,0 +1,6 @@ +namespace MediaBrowser.Controller.Library +{ + public class PlaybackStartEventArgs : PlaybackProgressEventArgs + { + } +} diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 079442215..55c330931 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -5,11 +5,11 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Events; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index e6609fae3..b04ebda8c 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using MediaBrowser.Model.Events; +using Jellyfin.Data.Events; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index e470d77b6..996ec27c0 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -7,12 +7,12 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Providers; namespace MediaBrowser.Controller.Providers diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 1a1e200fa..9237d21df 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -4,11 +4,11 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs b/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs deleted file mode 100644 index b1d74517e..000000000 --- a/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs +++ /dev/null @@ -1,29 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using MediaBrowser.Controller.Entities; - -namespace MediaBrowser.Controller.Subtitles -{ - public class SubtitleDownloadEventArgs - { - public BaseItem Item { get; set; } - - public string Format { get; set; } - - public string Language { get; set; } - - public bool IsForced { get; set; } - - public string Provider { get; set; } - } - - public class SubtitleDownloadFailureEventArgs - { - public BaseItem Item { get; set; } - - public string Provider { get; set; } - - public Exception Exception { get; set; } - } -} diff --git a/MediaBrowser.Controller/Subtitles/SubtitleDownloadFailureEventArgs.cs b/MediaBrowser.Controller/Subtitles/SubtitleDownloadFailureEventArgs.cs new file mode 100644 index 000000000..7687bf5b6 --- /dev/null +++ b/MediaBrowser.Controller/Subtitles/SubtitleDownloadFailureEventArgs.cs @@ -0,0 +1,14 @@ +using System; +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Subtitles +{ + public class SubtitleDownloadFailureEventArgs : EventArgs + { + public BaseItem Item { get; set; } + + public string Provider { get; set; } + + public Exception Exception { get; set; } + } +} diff --git a/MediaBrowser.Model/Activity/IActivityManager.cs b/MediaBrowser.Model/Activity/IActivityManager.cs index 9dab5e77b..2362f7e92 100644 --- a/MediaBrowser.Model/Activity/IActivityManager.cs +++ b/MediaBrowser.Model/Activity/IActivityManager.cs @@ -4,7 +4,7 @@ using System; using System.Linq; using System.Threading.Tasks; using Jellyfin.Data.Entities; -using MediaBrowser.Model.Events; +using Jellyfin.Data.Events; using MediaBrowser.Model.Querying; namespace MediaBrowser.Model.Activity diff --git a/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs b/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs index 76c9a4b04..05209e53d 100644 --- a/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs +++ b/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs @@ -1,7 +1,7 @@ #pragma warning disable CS1591 using System; -using MediaBrowser.Model.Events; +using Jellyfin.Data.Events; namespace MediaBrowser.Model.Dlna { diff --git a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs index b08acba2c..2f05e08c5 100644 --- a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs +++ b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs @@ -1,6 +1,6 @@ #nullable disable using System; -using MediaBrowser.Model.Events; +using Jellyfin.Data.Events; namespace MediaBrowser.Model.Tasks { diff --git a/MediaBrowser.Model/Tasks/ITaskManager.cs b/MediaBrowser.Model/Tasks/ITaskManager.cs index 363773ff7..02b29074e 100644 --- a/MediaBrowser.Model/Tasks/ITaskManager.cs +++ b/MediaBrowser.Model/Tasks/ITaskManager.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using MediaBrowser.Model.Events; +using Jellyfin.Data.Events; namespace MediaBrowser.Model.Tasks { diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index d9a84be5c..155e8cb8a 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -10,6 +10,7 @@ using System.Net.Mime; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; @@ -23,7 +24,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Providers; diff --git a/tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs b/tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs index c39ed07de..2029f88e9 100644 --- a/tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs +++ b/tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs @@ -72,6 +72,7 @@ namespace MediaBrowser.Api.Tests var startupConfig = Program.CreateAppConfiguration(commandLineOpts, appPaths); ILoggerFactory loggerFactory = new SerilogLoggerFactory(); + var serviceCollection = new ServiceCollection(); _disposableComponents.Add(loggerFactory); // Create the app host and initialize it @@ -80,10 +81,10 @@ namespace MediaBrowser.Api.Tests loggerFactory, commandLineOpts, new ManagedFileSystem(loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths), - new NetworkManager(loggerFactory.CreateLogger<NetworkManager>())); + new NetworkManager(loggerFactory.CreateLogger<NetworkManager>()), + serviceCollection); _disposableComponents.Add(appHost); - var serviceCollection = new ServiceCollection(); - appHost.Init(serviceCollection); + appHost.Init(); // Configure the web host builder Program.ConfigureWebHostBuilder(builder, appHost, serviceCollection, commandLineOpts, startupConfig, appPaths); |
