aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrobibero <cody@robibe.ro>2020-08-31 08:00:05 -0600
committercrobibero <cody@robibe.ro>2020-08-31 08:00:05 -0600
commitbd66fd25df772f3fd352fdda58e71474e255400a (patch)
treea6102da6a9f120d7c887f460c5d603be92ea199f
parent9626101c9f8d0acceb2ecf97bed502c272d6e4f6 (diff)
parent0424d09b8daf3626fd7de65b601e707afdb00599 (diff)
Merge remote-tracking branch 'upstream/master' into 3.1.7
-rw-r--r--.ci/azure-pipelines-package.yml31
-rw-r--r--Emby.Dlna/Eventing/DlnaEventManager.cs (renamed from Emby.Dlna/Eventing/EventManager.cs)4
-rw-r--r--Emby.Dlna/IConnectionManager.cs2
-rw-r--r--Emby.Dlna/IContentDirectory.cs2
-rw-r--r--Emby.Dlna/IDlnaEventManager.cs (renamed from Emby.Dlna/IEventManager.cs)2
-rw-r--r--Emby.Dlna/IMediaReceiverRegistrar.cs2
-rw-r--r--Emby.Dlna/PlayTo/PlayToController.cs2
-rw-r--r--Emby.Dlna/PlayTo/PlayToManager.cs2
-rw-r--r--Emby.Dlna/Service/BaseService.cs6
-rw-r--r--Emby.Dlna/Ssdp/DeviceDiscovery.cs2
-rw-r--r--Emby.Naming/Emby.Naming.csproj3
-rw-r--r--Emby.Notifications/NotificationEntryPoint.cs2
-rw-r--r--Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs590
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs159
-rw-r--r--Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs2
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs3
-rw-r--r--Emby.Server.Implementations/Devices/DeviceManager.cs2
-rw-r--r--Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs2
-rw-r--r--Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs2
-rw-r--r--Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs9
-rw-r--r--Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs210
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs2
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketConnection.cs8
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs2
-rw-r--r--Emby.Server.Implementations/Localization/Core/id.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/th.json5
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/TaskManager.cs2
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs117
-rw-r--r--Emby.Server.Implementations/Session/SessionWebSocketListener.cs2
-rw-r--r--Jellyfin.Api/Controllers/DlnaServerController.cs8
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs2
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs10
-rw-r--r--Jellyfin.Api/Controllers/PluginsController.cs8
-rw-r--r--Jellyfin.Api/Helpers/MediaInfoHelper.cs6
-rw-r--r--Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs2
-rw-r--r--Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs2
-rw-r--r--Jellyfin.Data/Events/GenericEventArgs.cs (renamed from MediaBrowser.Model/Events/GenericEventArgs.cs)16
-rw-r--r--Jellyfin.Data/Events/System/PendingRestartEventArgs.cs11
-rw-r--r--Jellyfin.Data/Events/Users/UserCreatedEventArgs.cs18
-rw-r--r--Jellyfin.Data/Events/Users/UserDeletedEventArgs.cs18
-rw-r--r--Jellyfin.Data/Events/Users/UserLockedOutEventArgs.cs18
-rw-r--r--Jellyfin.Data/Events/Users/UserPasswordChangedEventArgs.cs18
-rw-r--r--Jellyfin.Data/Events/Users/UserUpdatedEventArgs.cs18
-rw-r--r--Jellyfin.Data/Jellyfin.Data.csproj8
-rw-r--r--Jellyfin.Server.Implementations/Activity/ActivityManager.cs12
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Library/SubtitleDownloadFailureLogger.cs102
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationFailedLogger.cs52
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs49
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs104
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs106
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Session/SessionEndedLogger.cs54
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Session/SessionStartedLogger.cs54
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/System/PendingRestartNotifier.cs31
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs158
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs31
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs31
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedLogger.cs51
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs31
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledLogger.cs50
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs31
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs31
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs45
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs31
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUpdatedLogger.cs51
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Users/UserCreatedLogger.cs43
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedLogger.cs44
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs38
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Users/UserLockedOutLogger.cs47
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Users/UserPasswordChangedLogger.cs43
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs41
-rw-r--r--Jellyfin.Server.Implementations/Events/EventManager.cs60
-rw-r--r--Jellyfin.Server.Implementations/Events/EventingServiceCollectionExtensions.cs72
-rw-r--r--Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs2
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs43
-rw-r--r--Jellyfin.Server/CoreAppHost.cs27
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs3
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs5
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs13
-rw-r--r--Jellyfin.Server/Program.cs7
-rw-r--r--MediaBrowser.Common/IApplicationHost.cs3
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonDoubleConverter.cs56
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs40
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs56
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonNonStringKeyDictionaryConverter.cs82
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonNonStringKeyDictionaryConverterFactory.cs59
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonNullableInt32Converter.cs55
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonNullableInt64Converter.cs70
-rw-r--r--MediaBrowser.Common/Json/JsonDefaults.cs9
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj3
-rw-r--r--MediaBrowser.Common/Plugins/BasePlugin.cs11
-rw-r--r--MediaBrowser.Common/Plugins/IPlugin.cs13
-rw-r--r--MediaBrowser.Common/Updates/InstallationEventArgs.cs3
-rw-r--r--MediaBrowser.Controller/Devices/IDeviceManager.cs2
-rw-r--r--MediaBrowser.Controller/Events/IEventConsumer.cs20
-rw-r--r--MediaBrowser.Controller/Events/IEventManager.cs28
-rw-r--r--MediaBrowser.Controller/Events/Session/SessionEndedEventArgs.cs19
-rw-r--r--MediaBrowser.Controller/Events/Session/SessionStartedEventArgs.cs19
-rw-r--r--MediaBrowser.Controller/Events/Updates/PluginInstallationCancelledEventArgs.cs19
-rw-r--r--MediaBrowser.Controller/Events/Updates/PluginInstalledEventArgs.cs19
-rw-r--r--MediaBrowser.Controller/Events/Updates/PluginInstallingEventArgs.cs19
-rw-r--r--MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs19
-rw-r--r--MediaBrowser.Controller/Events/Updates/PluginUpdatedEventArgs.cs19
-rw-r--r--MediaBrowser.Controller/IServerApplicationHost.cs16
-rw-r--r--MediaBrowser.Controller/Library/IUserManager.cs22
-rw-r--r--MediaBrowser.Controller/Library/PlaybackStartEventArgs.cs9
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs2
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj3
-rw-r--r--MediaBrowser.Controller/Net/IHttpServer.cs2
-rw-r--r--MediaBrowser.Controller/Providers/IProviderManager.cs2
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs2
-rw-r--r--MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs29
-rw-r--r--MediaBrowser.Controller/Subtitles/SubtitleDownloadFailureEventArgs.cs26
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs6
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs1
-rw-r--r--MediaBrowser.Model/Activity/IActivityManager.cs4
-rw-r--r--MediaBrowser.Model/Dlna/IDeviceDiscovery.cs2
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj5
-rw-r--r--MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs2
-rw-r--r--MediaBrowser.Model/Tasks/ITaskManager.cs2
-rw-r--r--MediaBrowser.Model/Users/UserPolicy.cs12
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs2
-rwxr-xr-xbump_version18
-rw-r--r--fedora/jellyfin.spec3
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs3
-rw-r--r--tests/MediaBrowser.Api.Tests/JellyfinApplicationFactory.cs7
130 files changed, 2206 insertions, 1564 deletions
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 003d5baf0..4631badae 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -147,19 +147,42 @@ jobs:
displayName: 'Publish NuGet packages'
dependsOn:
- BuildPackage
- condition: and(succeeded('BuildPackage'), startsWith(variables['Build.SourceBranch'], 'refs/tags'))
+ condition: succeeded('BuildPackage')
pool:
vmImage: 'ubuntu-latest'
steps:
- - task: NuGetCommand@2
+ - task: DotNetCoreCLI@2
+ displayName: 'Build Stable Nuget packages'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
inputs:
command: 'pack'
- packagesToPack: Jellyfin.Data/Jellyfin.Data.csproj;MediaBrowser.Common/MediaBrowser.Common.csproj;MediaBrowser.Controller/MediaBrowser.Controller.csproj;MediaBrowser.Model/MediaBrowser.Model.csproj;Emby.Naming/Emby.Naming.csproj
- packDestination: '$(Build.ArtifactStagingDirectory)'
+ packagesToPack: 'Jellyfin.Data/Jellyfin.Data.csproj;MediaBrowser.Common/MediaBrowser.Common.csproj;MediaBrowser.Controller/MediaBrowser.Controller.csproj;MediaBrowser.Model/MediaBrowser.Model.csproj;Emby.Naming/Emby.Naming.csproj'
+ versioningScheme: 'off'
+
+ - task: DotNetCoreCLI@2
+ displayName: 'Build Unstable Nuget packages'
+ inputs:
+ command: 'custom'
+ projects: |
+ Jellyfin.Data/Jellyfin.Data.csproj
+ MediaBrowser.Common/MediaBrowser.Common.csproj
+ MediaBrowser.Controller/MediaBrowser.Controller.csproj
+ MediaBrowser.Model/MediaBrowser.Model.csproj
+ Emby.Naming/Emby.Naming.csproj
+ custom: 'pack'
+ arguments: '--version-suffix $(Build.BuildNumber) -o $(Build.ArtifactStagingDirectory)'
+
+ - task: PublishBuildArtifacts@1
+ displayName: 'Publish Nuget packages'
+ inputs:
+ pathToPublish: $(Build.ArtifactStagingDirectory)
+ artifactName: Jellyfin Nuget Packages
- task: NuGetCommand@2
+ displayName: 'Push Nuget packages to feed'
+ condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
inputs:
command: 'push'
packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg'
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.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index c017e76c7..5e2c6e3e3 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -23,8 +23,9 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId>
- <PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
+ <VersionPrefix>10.7.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
+ <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
<!-- Code Analyzers-->
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/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 48e2f5d4a..5bf740cfc 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -90,9 +90,6 @@ namespace Emby.Server.Implementations.Data
_typeMapper = new TypeMapper();
_jsonOptions = JsonDefaults.GetOptions();
- // GetItem throws NotSupportedException with this enabled, so hardcode false.
- _jsonOptions.IgnoreNullValues = false;
-
DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
}
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/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index d738047e0..7eae4e764 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -179,7 +179,7 @@ namespace Emby.Server.Implementations.HttpServer
return;
}
- WebSocketMessage<object> stub;
+ WebSocketMessage<object>? stub;
try
{
@@ -209,6 +209,12 @@ namespace Emby.Server.Implementations.HttpServer
return;
}
+ if (stub == null)
+ {
+ _logger.LogError("Error processing web socket message");
+ return;
+ }
+
// Tell the PipeReader how much of the buffer we have consumed
reader.AdvanceTo(buffer.End);
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/Localization/Core/id.json b/Emby.Server.Implementations/Localization/Core/id.json
index d2e27e379..b0dfc312e 100644
--- a/Emby.Server.Implementations/Localization/Core/id.json
+++ b/Emby.Server.Implementations/Localization/Core/id.json
@@ -22,7 +22,7 @@
"HeaderContinueWatching": "Lanjutkan Menonton",
"HeaderCameraUploads": "Unggahan Kamera",
"HeaderAlbumArtists": "Album Artis",
- "Genres": "Genre",
+ "Genres": "Aliran",
"Folders": "Folder",
"Favorites": "Favorit",
"Collections": "Koleksi",
diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json
index 576aaeb1b..42500670d 100644
--- a/Emby.Server.Implementations/Localization/Core/th.json
+++ b/Emby.Server.Implementations/Localization/Core/th.json
@@ -69,5 +69,8 @@
"AppDeviceValues": "App: {0}, อุปกรณ์: {1}",
"Albums": "อัลบั้ม",
"ScheduledTaskStartedWithName": "{0} เริ่มต้น",
- "ScheduledTaskFailedWithName": "{0} ล้มเหลว"
+ "ScheduledTaskFailedWithName": "{0} ล้มเหลว",
+ "Songs": "เพลง",
+ "Shows": "แสดง",
+ "ServerNameNeedsToBeRestarted": "{0} ต้องการรีสตาร์ท"
}
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/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index b4fe3bc8f..b115ac6cd 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -1356,7 +1356,7 @@ namespace Jellyfin.Api.Controllers
return string.Format(
CultureInfo.InvariantCulture,
- "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
+ "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size 2048 -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
inputModifier,
_encodingHelper.GetInputArgument(state, encodingOptions),
threads,
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 4548e202a..796d2d8aa 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -619,7 +619,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.Download)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult GetDownload([FromRoute] Guid itemId)
+ public async Task<ActionResult> GetDownload([FromRoute] Guid itemId)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -648,7 +648,7 @@ namespace Jellyfin.Api.Controllers
if (user != null)
{
- LogDownload(item, user, auth);
+ await LogDownloadAsync(item, user, auth).ConfigureAwait(false);
}
var path = item.Path;
@@ -861,17 +861,17 @@ namespace Jellyfin.Api.Controllers
: item;
}
- private void LogDownload(BaseItem item, User user, AuthorizationInfo auth)
+ private async Task LogDownloadAsync(BaseItem item, User user, AuthorizationInfo auth)
{
try
{
- _activityManager.Create(new ActivityLog(
+ await _activityManager.CreateAsync(new ActivityLog(
string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Username, item.Name),
"UserDownloadingContent",
auth.UserId)
{
ShortOverview = string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device),
- });
+ }).ConfigureAwait(false);
}
catch
{
diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs
index b2f34680b..a82f2621a 100644
--- a/Jellyfin.Api/Controllers/PluginsController.cs
+++ b/Jellyfin.Api/Controllers/PluginsController.cs
@@ -120,10 +120,14 @@ namespace Jellyfin.Api.Controllers
return NotFound();
}
- var configuration = (BasePluginConfiguration)await JsonSerializer.DeserializeAsync(Request.Body, plugin.ConfigurationType, _serializerOptions)
+ var configuration = (BasePluginConfiguration?)await JsonSerializer.DeserializeAsync(Request.Body, plugin.ConfigurationType, _serializerOptions)
.ConfigureAwait(false);
- plugin.UpdateConfiguration(configuration);
+ if (configuration != null)
+ {
+ plugin.UpdateConfiguration(configuration);
+ }
+
return NoContent();
}
diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
index d2d1855a4..3a736d1e8 100644
--- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs
+++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
@@ -127,7 +127,11 @@ namespace Jellyfin.Api.Helpers
{
// Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it
// Should we move this directly into MediaSourceManager?
- result.MediaSources = JsonSerializer.Deserialize<MediaSourceInfo[]>(JsonSerializer.SerializeToUtf8Bytes(mediaSources));
+ var mediaSourcesClone = JsonSerializer.Deserialize<MediaSourceInfo[]>(JsonSerializer.SerializeToUtf8Bytes(mediaSources));
+ if (mediaSourcesClone != null)
+ {
+ result.MediaSources = mediaSourcesClone;
+ }
result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
}
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.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj
index 9b50fe2aa..2617ca90a 100644
--- a/Jellyfin.Data/Jellyfin.Data.csproj
+++ b/Jellyfin.Data/Jellyfin.Data.csproj
@@ -7,6 +7,14 @@
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release' ">true</TreatWarningsAsErrors>
</PropertyGroup>
+ <PropertyGroup>
+ <Authors>Jellyfin Contributors</Authors>
+ <PackageId>Jellyfin.Data</PackageId>
+ <VersionPrefix>10.7.0</VersionPrefix>
+ <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
+ <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
+ </PropertyGroup>
+
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs
index 2deefbe81..abdd290d4 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
@@ -28,16 +28,6 @@ namespace Jellyfin.Server.Implementations.Activity
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
/// <inheritdoc/>
- public void Create(ActivityLog entry)
- {
- using var dbContext = _provider.CreateContext();
- dbContext.ActivityLogs.Add(entry);
- dbContext.SaveChanges();
-
- EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(ConvertToOldModel(entry)));
- }
-
- /// <inheritdoc/>
public async Task CreateAsync(ActivityLog entry)
{
await using var dbContext = _provider.CreateContext();
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..8f04baa08 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/>
@@ -407,13 +402,13 @@ namespace Jellyfin.Server.Implementations.Users
EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing),
AccessSchedules = user.AccessSchedules.ToArray(),
BlockedTags = user.GetPreference(PreferenceKind.BlockedTags),
- EnabledChannels = user.GetPreference(PreferenceKind.EnabledChannels),
+ EnabledChannels = user.GetPreference(PreferenceKind.EnabledChannels)?.Select(Guid.Parse).ToArray(),
EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices),
- EnabledFolders = user.GetPreference(PreferenceKind.EnabledFolders),
+ EnabledFolders = user.GetPreference(PreferenceKind.EnabledFolders)?.Select(Guid.Parse).ToArray(),
EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders),
SyncPlayAccess = user.SyncPlayAccess,
- BlockedChannels = user.GetPreference(PreferenceKind.BlockedChannels),
- BlockedMediaFolders = user.GetPreference(PreferenceKind.BlockedMediaFolders),
+ BlockedChannels = user.GetPreference(PreferenceKind.BlockedChannels)?.Select(Guid.Parse).ToArray(),
+ BlockedMediaFolders = user.GetPreference(PreferenceKind.BlockedMediaFolders)?.Select(Guid.Parse).ToArray(),
BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems).Select(Enum.Parse<UnratedItem>).ToArray()
}
};
@@ -740,9 +735,9 @@ namespace Jellyfin.Server.Implementations.Users
PreferenceKind.BlockUnratedItems,
policy.BlockUnratedItems?.Select(i => i.ToString()).ToArray() ?? Array.Empty<string>());
user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
- user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
+ user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels?.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray());
user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
- user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
+ user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders?.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray());
user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
dbContext.Update(user);
@@ -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/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 1f9fc7078..0fd599cfc 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -168,7 +168,8 @@ namespace Jellyfin.Server.Extensions
// From JsonDefaults
options.JsonSerializerOptions.ReadCommentHandling = jsonOptions.ReadCommentHandling;
options.JsonSerializerOptions.WriteIndented = jsonOptions.WriteIndented;
- options.JsonSerializerOptions.IgnoreNullValues = jsonOptions.IgnoreNullValues;
+ options.JsonSerializerOptions.DefaultIgnoreCondition = jsonOptions.DefaultIgnoreCondition;
+ options.JsonSerializerOptions.NumberHandling = jsonOptions.NumberHandling;
options.JsonSerializerOptions.Converters.Clear();
foreach (var converter in jsonOptions.Converters)
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
index b15ccf01e..7f57358ec 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
@@ -81,6 +81,11 @@ namespace Jellyfin.Server.Migrations.Routines
foreach (var result in results)
{
var dto = JsonSerializer.Deserialize<DisplayPreferencesDto>(result[3].ToString(), _jsonOptions);
+ if (dto == null)
+ {
+ continue;
+ }
+
var chromecastVersion = dto.CustomPrefs.TryGetValue("chromecastVersion", out var version)
? chromecastDict[version]
: ChromecastVersion.Stable;
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
index 274e6ab73..74c550331 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
@@ -1,5 +1,7 @@
using System;
+using System.Globalization;
using System.IO;
+using System.Linq;
using Emby.Server.Implementations.Data;
using Emby.Server.Implementations.Serialization;
using Jellyfin.Data.Entities;
@@ -74,7 +76,12 @@ namespace Jellyfin.Server.Migrations.Routines
foreach (var entry in queryResult)
{
- UserMockup mockup = JsonSerializer.Deserialize<UserMockup>(entry[2].ToBlob(), JsonDefaults.GetOptions());
+ UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry[2].ToBlob(), JsonDefaults.GetOptions());
+ if (mockup == null)
+ {
+ continue;
+ }
+
var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name);
var config = File.Exists(Path.Combine(userDataDir, "config.xml"))
@@ -161,9 +168,9 @@ namespace Jellyfin.Server.Migrations.Routines
}
user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
- user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
+ user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels?.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray());
user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
- user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
+ user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders?.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray());
user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
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/Json/Converters/JsonDoubleConverter.cs b/MediaBrowser.Common/Json/Converters/JsonDoubleConverter.cs
deleted file mode 100644
index 56c0ecbe9..000000000
--- a/MediaBrowser.Common/Json/Converters/JsonDoubleConverter.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System;
-using System.Buffers;
-using System.Buffers.Text;
-using System.Globalization;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace MediaBrowser.Common.Json.Converters
-{
- /// <summary>
- /// Double to String JSON converter.
- /// Web client send quoted doubles.
- /// </summary>
- public class JsonDoubleConverter : JsonConverter<double>
- {
- /// <summary>
- /// Read JSON string as double.
- /// </summary>
- /// <param name="reader"><see cref="Utf8JsonReader"/>.</param>
- /// <param name="typeToConvert">Type.</param>
- /// <param name="options">Options.</param>
- /// <returns>Parsed value.</returns>
- public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- if (reader.TokenType == JsonTokenType.String)
- {
- // try to parse number directly from bytes
- var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
- if (Utf8Parser.TryParse(span, out double number, out var bytesConsumed) && span.Length == bytesConsumed)
- {
- return number;
- }
-
- // try to parse from a string if the above failed, this covers cases with other escaped/UTF characters
- if (double.TryParse(reader.GetString(), out number))
- {
- return number;
- }
- }
-
- // fallback to default handling
- return reader.GetDouble();
- }
-
- /// <summary>
- /// Write double to JSON string.
- /// </summary>
- /// <param name="writer"><see cref="Utf8JsonWriter"/>.</param>
- /// <param name="value">Value to write.</param>
- /// <param name="options">Options.</param>
- public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
- {
- writer.WriteNumberValue(value);
- }
- }
-}
diff --git a/MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs b/MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs
deleted file mode 100644
index 7ed9d6766..000000000
--- a/MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System;
-using System.Buffers;
-using System.Buffers.Text;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace MediaBrowser.Common.Json.Converters
-{
- /// <summary>
- /// Converts a int32 object or value to/from JSON.
- /// </summary>
- public class JsonInt32Converter : JsonConverter<int>
- {
- /// <inheritdoc />
- public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- if (reader.TokenType == JsonTokenType.String)
- {
- ReadOnlySpan<byte> span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
- if (Utf8Parser.TryParse(span, out int number, out int bytesConsumed) && span.Length == bytesConsumed)
- {
- return number;
- }
-
- if (int.TryParse(reader.GetString(), out number))
- {
- return number;
- }
- }
-
- return reader.GetInt32();
- }
-
- /// <inheritdoc />
- public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
- {
- writer.WriteNumberValue(value);
- }
- }
-}
diff --git a/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs b/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs
deleted file mode 100644
index 427f1fa7e..000000000
--- a/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System;
-using System.Buffers;
-using System.Buffers.Text;
-using System.Globalization;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace MediaBrowser.Common.Json.Converters
-{
- /// <summary>
- /// Parse JSON string as long.
- /// Javascript does not support 64-bit integers.
- /// </summary>
- public class JsonInt64Converter : JsonConverter<long>
- {
- /// <summary>
- /// Read JSON string as int64.
- /// </summary>
- /// <param name="reader"><see cref="Utf8JsonReader"/>.</param>
- /// <param name="type">Type.</param>
- /// <param name="options">Options.</param>
- /// <returns>Parsed value.</returns>
- public override long Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
- {
- if (reader.TokenType == JsonTokenType.String)
- {
- // try to parse number directly from bytes
- var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
- if (Utf8Parser.TryParse(span, out long number, out var bytesConsumed) && span.Length == bytesConsumed)
- {
- return number;
- }
-
- // try to parse from a string if the above failed, this covers cases with other escaped/UTF characters
- if (long.TryParse(reader.GetString(), out number))
- {
- return number;
- }
- }
-
- // fallback to default handling
- return reader.GetInt64();
- }
-
- /// <summary>
- /// Write long to JSON long.
- /// </summary>
- /// <param name="writer"><see cref="Utf8JsonWriter"/>.</param>
- /// <param name="value">Value to write.</param>
- /// <param name="options">Options.</param>
- public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
- {
- writer.WriteNumberValue(value);
- }
- }
-}
diff --git a/MediaBrowser.Common/Json/Converters/JsonNonStringKeyDictionaryConverter.cs b/MediaBrowser.Common/Json/Converters/JsonNonStringKeyDictionaryConverter.cs
deleted file mode 100644
index 8053461f0..000000000
--- a/MediaBrowser.Common/Json/Converters/JsonNonStringKeyDictionaryConverter.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-#nullable enable
-
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Reflection;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace MediaBrowser.Common.Json.Converters
-{
- /// <summary>
- /// Converter for Dictionaries without string key.
- /// TODO This can be removed when System.Text.Json supports Dictionaries with non-string keys.
- /// </summary>
- /// <typeparam name="TKey">Type of key.</typeparam>
- /// <typeparam name="TValue">Type of value.</typeparam>
- internal sealed class JsonNonStringKeyDictionaryConverter<TKey, TValue> : JsonConverter<IDictionary<TKey, TValue>>
- {
- /// <summary>
- /// Read JSON.
- /// </summary>
- /// <param name="reader">The Utf8JsonReader.</param>
- /// <param name="typeToConvert">The type to convert.</param>
- /// <param name="options">The json serializer options.</param>
- /// <returns>Typed dictionary.</returns>
- /// <exception cref="NotSupportedException">Dictionary key type not supported.</exception>
- public override IDictionary<TKey, TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- var convertedType = typeof(Dictionary<,>).MakeGenericType(typeof(string), typeToConvert.GenericTypeArguments[1]);
- var value = JsonSerializer.Deserialize(ref reader, convertedType, options);
- var instance = (Dictionary<TKey, TValue>)Activator.CreateInstance(
- typeToConvert,
- BindingFlags.Instance | BindingFlags.Public,
- null,
- null,
- CultureInfo.CurrentCulture);
- var enumerator = (IEnumerator)convertedType.GetMethod("GetEnumerator")!.Invoke(value, null);
- var parse = typeof(TKey).GetMethod(
- "Parse",
- 0,
- BindingFlags.Public | BindingFlags.Static,
- null,
- CallingConventions.Any,
- new[] { typeof(string) },
- null);
- if (parse == null)
- {
- throw new NotSupportedException($"{typeof(TKey)} as TKey in IDictionary<TKey, TValue> is not supported.");
- }
-
- while (enumerator.MoveNext())
- {
- var element = (KeyValuePair<string?, TValue>)enumerator.Current;
- instance.Add((TKey)parse.Invoke(null, new[] { (object?)element.Key }), element.Value);
- }
-
- return instance;
- }
-
- /// <summary>
- /// Write dictionary as Json.
- /// </summary>
- /// <param name="writer">The Utf8JsonWriter.</param>
- /// <param name="value">The dictionary value.</param>
- /// <param name="options">The Json serializer options.</param>
- public override void Write(Utf8JsonWriter writer, IDictionary<TKey, TValue> value, JsonSerializerOptions options)
- {
- var convertedDictionary = new Dictionary<string?, TValue>(value.Count);
- foreach (var (k, v) in value)
- {
- if (k != null)
- {
- convertedDictionary[k.ToString()] = v;
- }
- }
-
- JsonSerializer.Serialize(writer, convertedDictionary, options);
- }
- }
-}
diff --git a/MediaBrowser.Common/Json/Converters/JsonNonStringKeyDictionaryConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonNonStringKeyDictionaryConverterFactory.cs
deleted file mode 100644
index 52f360740..000000000
--- a/MediaBrowser.Common/Json/Converters/JsonNonStringKeyDictionaryConverterFactory.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-#nullable enable
-
-using System;
-using System.Collections;
-using System.Globalization;
-using System.Reflection;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace MediaBrowser.Common.Json.Converters
-{
- /// <summary>
- /// https://github.com/dotnet/runtime/issues/30524#issuecomment-524619972.
- /// TODO This can be removed when System.Text.Json supports Dictionaries with non-string keys.
- /// </summary>
- internal sealed class JsonNonStringKeyDictionaryConverterFactory : JsonConverterFactory
- {
- /// <summary>
- /// Only convert objects that implement IDictionary and do not have string keys.
- /// </summary>
- /// <param name="typeToConvert">Type convert.</param>
- /// <returns>Conversion ability.</returns>
- public override bool CanConvert(Type typeToConvert)
- {
- if (!typeToConvert.IsGenericType)
- {
- return false;
- }
-
- // Let built in converter handle string keys
- if (typeToConvert.GenericTypeArguments[0] == typeof(string))
- {
- return false;
- }
-
- // Only support objects that implement IDictionary
- return typeToConvert.GetInterface(nameof(IDictionary)) != null;
- }
-
- /// <summary>
- /// Create converter for generic dictionary type.
- /// </summary>
- /// <param name="typeToConvert">Type to convert.</param>
- /// <param name="options">Json serializer options.</param>
- /// <returns>JsonConverter for given type.</returns>
- public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
- {
- var converterType = typeof(JsonNonStringKeyDictionaryConverter<,>)
- .MakeGenericType(typeToConvert.GenericTypeArguments[0], typeToConvert.GenericTypeArguments[1]);
- var converter = (JsonConverter)Activator.CreateInstance(
- converterType,
- BindingFlags.Instance | BindingFlags.Public,
- null,
- null,
- CultureInfo.CurrentCulture);
- return converter;
- }
- }
-}
diff --git a/MediaBrowser.Common/Json/Converters/JsonNullableInt32Converter.cs b/MediaBrowser.Common/Json/Converters/JsonNullableInt32Converter.cs
deleted file mode 100644
index c1660fe76..000000000
--- a/MediaBrowser.Common/Json/Converters/JsonNullableInt32Converter.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-using System.Buffers;
-using System.Buffers.Text;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace MediaBrowser.Common.Json.Converters
-{
- /// <summary>
- /// Converts a nullable int32 object or value to/from JSON.
- /// </summary>
- public class JsonNullableInt32Converter : JsonConverter<int?>
- {
- /// <inheritdoc />
- public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- if (reader.TokenType == JsonTokenType.String)
- {
- ReadOnlySpan<byte> span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
- if (Utf8Parser.TryParse(span, out int number, out int bytesConsumed) && span.Length == bytesConsumed)
- {
- return number;
- }
-
- var stringValue = reader.GetString().AsSpan();
-
- // value is null or empty, just return null.
- if (stringValue.IsEmpty)
- {
- return null;
- }
-
- if (int.TryParse(stringValue, out number))
- {
- return number;
- }
- }
-
- return reader.GetInt32();
- }
-
- /// <inheritdoc />
- public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options)
- {
- if (value is null)
- {
- writer.WriteNullValue();
- }
- else
- {
- writer.WriteNumberValue(value.Value);
- }
- }
- }
-}
diff --git a/MediaBrowser.Common/Json/Converters/JsonNullableInt64Converter.cs b/MediaBrowser.Common/Json/Converters/JsonNullableInt64Converter.cs
deleted file mode 100644
index 53e5f6e9d..000000000
--- a/MediaBrowser.Common/Json/Converters/JsonNullableInt64Converter.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using System;
-using System.Buffers;
-using System.Buffers.Text;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace MediaBrowser.Common.Json.Converters
-{
- /// <summary>
- /// Parse JSON string as nullable long.
- /// Javascript does not support 64-bit integers.
- /// </summary>
- public class JsonNullableInt64Converter : JsonConverter<long?>
- {
- /// <summary>
- /// Read JSON string as int64.
- /// </summary>
- /// <param name="reader"><see cref="Utf8JsonReader"/>.</param>
- /// <param name="type">Type.</param>
- /// <param name="options">Options.</param>
- /// <returns>Parsed value.</returns>
- public override long? Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
- {
- if (reader.TokenType == JsonTokenType.String)
- {
- // try to parse number directly from bytes
- var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
- if (Utf8Parser.TryParse(span, out long number, out var bytesConsumed) && span.Length == bytesConsumed)
- {
- return number;
- }
-
- var stringValue = reader.GetString().AsSpan();
-
- // value is null or empty, just return null.
- if (stringValue.IsEmpty)
- {
- return null;
- }
-
- // try to parse from a string if the above failed, this covers cases with other escaped/UTF characters
- if (long.TryParse(stringValue, out number))
- {
- return number;
- }
- }
-
- // fallback to default handling
- return reader.GetInt64();
- }
-
- /// <summary>
- /// Write long to JSON long.
- /// </summary>
- /// <param name="writer"><see cref="Utf8JsonWriter"/>.</param>
- /// <param name="value">Value to write.</param>
- /// <param name="options">Options.</param>
- public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options)
- {
- if (value is null)
- {
- writer.WriteNullValue();
- }
- else
- {
- writer.WriteNumberValue(value.Value);
- }
- }
- }
-}
diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs
index 891715b3d..9d30927db 100644
--- a/MediaBrowser.Common/Json/JsonDefaults.cs
+++ b/MediaBrowser.Common/Json/JsonDefaults.cs
@@ -25,17 +25,12 @@ namespace MediaBrowser.Common.Json
{
ReadCommentHandling = JsonCommentHandling.Disallow,
WriteIndented = false,
- IgnoreNullValues = true
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ NumberHandling = JsonNumberHandling.AllowReadingFromString
};
options.Converters.Add(new JsonGuidConverter());
- options.Converters.Add(new JsonInt32Converter());
- options.Converters.Add(new JsonNullableInt32Converter());
options.Converters.Add(new JsonStringEnumConverter());
- options.Converters.Add(new JsonNonStringKeyDictionaryConverterFactory());
- options.Converters.Add(new JsonInt64Converter());
- options.Converters.Add(new JsonNullableInt64Converter());
- options.Converters.Add(new JsonDoubleConverter());
return options;
}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index e0e271e3e..e4bc93abc 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -8,8 +8,9 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId>
- <PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
+ <VersionPrefix>10.7.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
+ <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
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..5c4ab5d8d
--- /dev/null
+++ b/MediaBrowser.Controller/Events/IEventConsumer.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Events
+{
+ /// <summary>
+ /// An interface representing a type that consumes events of type <c>T</c>.
+ /// </summary>
+ /// <typeparam name="T">The type of events this consumes.</typeparam>
+ public interface IEventConsumer<in T>
+ where T : EventArgs
+ {
+ /// <summary>
+ /// A method that is called when an event of type <c>T</c> is fired.
+ /// </summary>
+ /// <param name="eventArgs">The event.</param>
+ /// <returns>A task representing the consumption of the event.</returns>
+ 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..ac372bceb
--- /dev/null
+++ b/MediaBrowser.Controller/Library/PlaybackStartEventArgs.cs
@@ -0,0 +1,9 @@
+namespace MediaBrowser.Controller.Library
+{
+ /// <summary>
+ /// An event that occurs when playback is started.
+ /// </summary>
+ 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/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 3dabc37ef..f3563f148 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -8,8 +8,9 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId>
- <PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
+ <VersionPrefix>10.7.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
+ <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
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..ce8141219
--- /dev/null
+++ b/MediaBrowser.Controller/Subtitles/SubtitleDownloadFailureEventArgs.cs
@@ -0,0 +1,26 @@
+using System;
+using MediaBrowser.Controller.Entities;
+
+namespace MediaBrowser.Controller.Subtitles
+{
+ /// <summary>
+ /// An event that occurs when subtitle downloading fails.
+ /// </summary>
+ public class SubtitleDownloadFailureEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Gets or sets the item.
+ /// </summary>
+ public BaseItem Item { get; set; }
+
+ /// <summary>
+ /// Gets or sets the provider.
+ /// </summary>
+ public string Provider { get; set; }
+
+ /// <summary>
+ /// Gets or sets the exception.
+ /// </summary>
+ public Exception Exception { get; set; }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 9d01da40f..5a3a9185d 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Json;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.MediaEncoding.Probing;
@@ -54,6 +55,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly object _runningProcessesLock = new object();
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
+ // MediaEncoder is registered as a Singleton
+ private readonly JsonSerializerOptions _jsonSerializerOptions;
+
private List<string> _encoders = new List<string>();
private List<string> _decoders = new List<string>();
private List<string> _hwaccels = new List<string>();
@@ -75,6 +79,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_localization = localization;
_encodingHelperFactory = encodingHelperFactory;
_startupOptionFFmpegPath = config.GetValue<string>(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty;
+ _jsonSerializerOptions = JsonDefaults.GetOptions();
}
private EncodingHelper EncodingHelper => _encodingHelperFactory.Value;
@@ -414,6 +419,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
result = await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(
process.StandardOutput.BaseStream,
+ _jsonSerializerOptions,
cancellationToken: cancellationToken).ConfigureAwait(false);
}
catch
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
index 8996d3b09..b7b23deff 100644
--- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
+++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
@@ -133,7 +133,6 @@ namespace MediaBrowser.MediaEncoding.Probing
/// </summary>
/// <value>The bits_per_raw_sample.</value>
[JsonPropertyName("bits_per_raw_sample")]
- [JsonConverter(typeof(JsonInt32Converter))]
public int BitsPerRawSample { get; set; }
/// <summary>
diff --git a/MediaBrowser.Model/Activity/IActivityManager.cs b/MediaBrowser.Model/Activity/IActivityManager.cs
index 9dab5e77b..d5344494e 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
@@ -13,8 +13,6 @@ namespace MediaBrowser.Model.Activity
{
event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
- void Create(ActivityLog entry);
-
Task CreateAsync(ActivityLog entry);
QueryResult<ActivityLogEntry> GetPagedResult(int? startIndex, int? limit);
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/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 4de932982..3515fc9fe 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -8,8 +8,9 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId>
- <PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
+ <VersionPrefix>10.7.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
+ <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup>
@@ -25,7 +26,7 @@
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.7" />
<PackageReference Include="System.Globalization" Version="4.3.0" />
- <PackageReference Include="System.Text.Json" Version="4.7.2" />
+ <PackageReference Include="System.Text.Json" Version="5.0.0-preview.8.20407.11" />
</ItemGroup>
<ItemGroup>
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.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs
index caf2e0f54..a1f01f7e8 100644
--- a/MediaBrowser.Model/Users/UserPolicy.cs
+++ b/MediaBrowser.Model/Users/UserPolicy.cs
@@ -80,11 +80,11 @@ namespace MediaBrowser.Model.Users
public bool EnableAllDevices { get; set; }
- public string[] EnabledChannels { get; set; }
+ public Guid[] EnabledChannels { get; set; }
public bool EnableAllChannels { get; set; }
- public string[] EnabledFolders { get; set; }
+ public Guid[] EnabledFolders { get; set; }
public bool EnableAllFolders { get; set; }
@@ -94,9 +94,9 @@ namespace MediaBrowser.Model.Users
public bool EnablePublicSharing { get; set; }
- public string[] BlockedMediaFolders { get; set; }
+ public Guid[] BlockedMediaFolders { get; set; }
- public string[] BlockedChannels { get; set; }
+ public Guid[] BlockedChannels { get; set; }
public int RemoteClientBitrateLimit { get; set; }
@@ -145,10 +145,10 @@ namespace MediaBrowser.Model.Users
LoginAttemptsBeforeLockout = -1;
EnableAllChannels = true;
- EnabledChannels = Array.Empty<string>();
+ EnabledChannels = Array.Empty<Guid>();
EnableAllFolders = true;
- EnabledFolders = Array.Empty<string>();
+ EnabledFolders = Array.Empty<Guid>();
EnabledDevices = Array.Empty<string>();
EnableAllDevices = true;
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/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs
index 3550614dd..7f10e6922 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs
@@ -740,11 +740,11 @@ namespace MediaBrowser.Providers.Music
// MusicBrainz request a contact email address is supplied, as comment, in user agent field:
// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent
- options.Headers.UserAgent.Add(new ProductInfoHeaderValue(string.Format(
+ options.Headers.UserAgent.ParseAdd(string.Format(
CultureInfo.InvariantCulture,
"{0} ( {1} )",
_appHost.ApplicationUserAgent,
- _appHost.ApplicationUserAgentAddress)));
+ _appHost.ApplicationUserAgentAddress));
HttpResponseMessage response;
var attempts = 0u;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
index 51c0b7e83..d8918bb6b 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
@@ -382,7 +382,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
/// </summary>
internal Task<HttpResponseMessage> GetMovieDbResponse(HttpRequestMessage message)
{
- message.Headers.UserAgent.Add(new ProductInfoHeaderValue(_appHost.ApplicationUserAgent));
+ message.Headers.UserAgent.ParseAdd(_appHost.ApplicationUserAgent);
return _httpClientFactory.CreateClient().SendAsync(message);
}
diff --git a/bump_version b/bump_version
index 1c943f691..d2de5a0bd 100755
--- a/bump_version
+++ b/bump_version
@@ -20,6 +20,8 @@ fi
shared_version_file="./SharedVersion.cs"
build_file="./build.yaml"
+# csproj files for nuget packages
+jellyfin_subprojects=( MediaBrowser.Common/MediaBrowser.Common.csproj Jellyfin.Data/Jellyfin.Data.csproj MediaBrowser.Controller/MediaBrowser.Controller.csproj MediaBrowser.Model/MediaBrowser.Model.csproj Emby.Naming/Emby.Naming.csproj )
new_version="$1"
@@ -45,6 +47,22 @@ echo $old_version
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
sed -i "s/${old_version_sed}/${new_version}/g" ${build_file}
+# update nuget package version
+for subproject in ${jellyfin_subprojects[@]}; do
+do
+ echo ${subproject}
+ # Parse the version from the *.csproj file
+ old_version="$(
+ grep "VersionPrefix" ${subproject} \
+ | awk '{$1=$1};1' \
+ | sed -E 's/<VersionPrefix>([0-9\.]+[-a-z0-9]*)<\/VersionPrefix>/\1/'
+ )"
+ echo old nuget version: $old_version
+
+ # Set the nuget version to the specified new_version
+ sed -i "s|${old_version}|${new_version}|g" ${subproject}
+done
+
if [[ ${new_version} == *"-"* ]]; then
new_version_deb="$( sed 's/-/~/g' <<<"${new_version}" )"
else
diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec
index b0a729a9e..a1e0b5f14 100644
--- a/fedora/jellyfin.spec
+++ b/fedora/jellyfin.spec
@@ -74,6 +74,9 @@ EOF
%{__install} -D -m 0755 %{SOURCE14} %{buildroot}%{_libexecdir}/jellyfin/restart.sh
%{__install} -D -m 0644 %{SOURCE16} %{buildroot}%{_prefix}/lib/firewalld/services/jellyfin.xml
+%files
+# empty as this is just a meta-package
+
%files server
%attr(755,root,root) %{_bindir}/jellyfin
%{_libdir}/jellyfin/*.json
diff --git a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
index 2032f6cec..c39ef0ce9 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
@@ -1,6 +1,7 @@
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
+using MediaBrowser.Common.Json;
using MediaBrowser.MediaEncoding.Probing;
using Xunit;
@@ -15,7 +16,7 @@ namespace Jellyfin.MediaEncoding.Tests
var path = Path.Join("Test Data", fileName);
using (var stream = File.OpenRead(path))
{
- await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(stream).ConfigureAwait(false);
+ await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(stream, JsonDefaults.GetOptions()).ConfigureAwait(false);
}
}
}
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);