diff --git a/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs b/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs index 87ec14d958..82c80070a5 100644 --- a/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs +++ b/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Text; using Emby.Dlna.ConnectionManager; using Emby.Dlna.ContentDirectory; +using Emby.Dlna.Main; using Emby.Dlna.MediaReceiverRegistrar; using Emby.Dlna.Ssdp; using MediaBrowser.Common.Net; @@ -65,5 +66,7 @@ public static class DlnaServiceCollectionExtensions { IsShared = true }); + + services.AddHostedService(); } } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs deleted file mode 100644 index aa70124870..0000000000 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ /dev/null @@ -1,363 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System; -using System.Globalization; -using System.Linq; -using System.Net.Http; -using System.Net.Sockets; -using System.Threading.Tasks; -using Emby.Dlna.PlayTo; -using Emby.Dlna.Ssdp; -using Jellyfin.Networking.Configuration; -using Jellyfin.Networking.Extensions; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Globalization; -using Microsoft.Extensions.Logging; -using Rssdp; -using Rssdp.Infrastructure; - -namespace Emby.Dlna.Main -{ - public sealed class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup - { - private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; - private readonly IServerApplicationHost _appHost; - private readonly ISessionManager _sessionManager; - private readonly IHttpClientFactory _httpClientFactory; - private readonly ILibraryManager _libraryManager; - private readonly IUserManager _userManager; - private readonly IDlnaManager _dlnaManager; - private readonly IImageProcessor _imageProcessor; - private readonly IUserDataManager _userDataManager; - private readonly ILocalizationManager _localization; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IMediaEncoder _mediaEncoder; - private readonly IDeviceDiscovery _deviceDiscovery; - private readonly ISsdpCommunicationsServer _communicationsServer; - private readonly INetworkManager _networkManager; - private readonly object _syncLock = new(); - private readonly bool _disabled; - - private PlayToManager _manager; - private SsdpDevicePublisher _publisher; - - private bool _disposed; - - public DlnaEntryPoint( - IServerConfigurationManager config, - ILoggerFactory loggerFactory, - IServerApplicationHost appHost, - ISessionManager sessionManager, - IHttpClientFactory httpClientFactory, - ILibraryManager libraryManager, - IUserManager userManager, - IDlnaManager dlnaManager, - IImageProcessor imageProcessor, - IUserDataManager userDataManager, - ILocalizationManager localizationManager, - IMediaSourceManager mediaSourceManager, - IDeviceDiscovery deviceDiscovery, - IMediaEncoder mediaEncoder, - ISsdpCommunicationsServer communicationsServer, - INetworkManager networkManager) - { - _config = config; - _appHost = appHost; - _sessionManager = sessionManager; - _httpClientFactory = httpClientFactory; - _libraryManager = libraryManager; - _userManager = userManager; - _dlnaManager = dlnaManager; - _imageProcessor = imageProcessor; - _userDataManager = userDataManager; - _localization = localizationManager; - _mediaSourceManager = mediaSourceManager; - _deviceDiscovery = deviceDiscovery; - _mediaEncoder = mediaEncoder; - _communicationsServer = communicationsServer; - _networkManager = networkManager; - _logger = loggerFactory.CreateLogger(); - - var netConfig = config.GetConfiguration(NetworkConfigurationStore.StoreKey); - _disabled = appHost.ListenWithHttps && netConfig.RequireHttps; - - if (_disabled && _config.GetDlnaConfiguration().EnableServer) - { - _logger.LogError("The DLNA specification does not support HTTPS."); - } - } - - public async Task RunAsync() - { - await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false); - - if (_disabled) - { - // No use starting as dlna won't work, as we're running purely on HTTPS. - return; - } - - ReloadComponents(); - - _config.NamedConfigurationUpdated += OnNamedConfigurationUpdated; - } - - private void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) - { - if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase)) - { - ReloadComponents(); - } - } - - private void ReloadComponents() - { - var options = _config.GetDlnaConfiguration(); - StartDeviceDiscovery(); - - if (options.EnableServer) - { - StartDevicePublisher(options); - } - else - { - DisposeDevicePublisher(); - } - - if (options.EnablePlayTo) - { - StartPlayToManager(); - } - else - { - DisposePlayToManager(); - } - } - - private void StartDeviceDiscovery() - { - try - { - ((DeviceDiscovery)_deviceDiscovery).Start(_communicationsServer); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error starting device discovery"); - } - } - - public void StartDevicePublisher(Configuration.DlnaOptions options) - { - if (_publisher is not null) - { - return; - } - - try - { - _publisher = new SsdpDevicePublisher( - _communicationsServer, - Environment.OSVersion.Platform.ToString(), - // Can not use VersionString here since that includes OS and version - Environment.OSVersion.Version.ToString(), - _config.GetDlnaConfiguration().SendOnlyMatchedHost) - { - LogFunction = (msg) => _logger.LogDebug("{Msg}", msg), - SupportPnpRootDevice = false - }; - - RegisterServerEndpoints(); - - if (options.BlastAliveMessages) - { - _publisher.StartSendingAliveNotifications(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds)); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error registering endpoint"); - } - } - - private void RegisterServerEndpoints() - { - var udn = CreateUuid(_appHost.SystemId); - var descriptorUri = "/dlna/" + udn + "/description.xml"; - - // Only get bind addresses in LAN - // IPv6 is currently unsupported - var validInterfaces = _networkManager.GetInternalBindAddresses() - .Where(x => x.Address is not null) - .Where(x => x.AddressFamily != AddressFamily.InterNetworkV6) - .ToList(); - - if (validInterfaces.Count == 0) - { - // No interfaces returned, fall back to loopback - validInterfaces = _networkManager.GetLoopbacks().ToList(); - } - - foreach (var intf in validInterfaces) - { - var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; - - _logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, intf.Address); - - var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(intf.Address, false) + descriptorUri); - - var device = new SsdpRootDevice - { - CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info. - Location = uri.Uri, // Must point to the URL that serves your devices UPnP description document. - Address = intf.Address, - PrefixLength = NetworkExtensions.MaskToCidr(intf.Subnet.Prefix), - FriendlyName = "Jellyfin", - Manufacturer = "Jellyfin", - ModelName = "Jellyfin Server", - Uuid = udn - // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc. - }; - - SetProperties(device, fullService); - _publisher.AddDevice(device); - - var embeddedDevices = new[] - { - "urn:schemas-upnp-org:service:ContentDirectory:1", - "urn:schemas-upnp-org:service:ConnectionManager:1", - // "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1" - }; - - foreach (var subDevice in embeddedDevices) - { - var embeddedDevice = new SsdpEmbeddedDevice - { - FriendlyName = device.FriendlyName, - Manufacturer = device.Manufacturer, - ModelName = device.ModelName, - Uuid = udn - // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc. - }; - - SetProperties(embeddedDevice, subDevice); - device.AddDevice(embeddedDevice); - } - } - } - - private static string CreateUuid(string text) - { - if (!Guid.TryParse(text, out var guid)) - { - guid = text.GetMD5(); - } - - return guid.ToString("D", CultureInfo.InvariantCulture); - } - - private static void SetProperties(SsdpDevice device, string fullDeviceType) - { - var serviceParts = fullDeviceType - .Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase) - .Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase) - .Split(':'); - - device.DeviceTypeNamespace = serviceParts[0].Replace('.', '-'); - device.DeviceClass = serviceParts[1]; - device.DeviceType = serviceParts[2]; - } - - private void StartPlayToManager() - { - lock (_syncLock) - { - if (_manager is not null) - { - return; - } - - try - { - _manager = new PlayToManager( - _logger, - _sessionManager, - _libraryManager, - _userManager, - _dlnaManager, - _appHost, - _imageProcessor, - _deviceDiscovery, - _httpClientFactory, - _userDataManager, - _localization, - _mediaSourceManager, - _mediaEncoder); - - _manager.Start(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error starting PlayTo manager"); - } - } - } - - private void DisposePlayToManager() - { - lock (_syncLock) - { - if (_manager is not null) - { - try - { - _logger.LogInformation("Disposing PlayToManager"); - _manager.Dispose(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error disposing PlayTo manager"); - } - - _manager = null; - } - } - } - - public void DisposeDevicePublisher() - { - if (_publisher is not null) - { - _logger.LogInformation("Disposing SsdpDevicePublisher"); - _publisher.Dispose(); - _publisher = null; - } - } - - /// - public void Dispose() - { - if (_disposed) - { - return; - } - - DisposeDevicePublisher(); - DisposePlayToManager(); - _disposed = true; - } - } -} diff --git a/Emby.Dlna/Main/DlnaHost.cs b/Emby.Dlna/Main/DlnaHost.cs new file mode 100644 index 0000000000..3896b74a1b --- /dev/null +++ b/Emby.Dlna/Main/DlnaHost.cs @@ -0,0 +1,389 @@ +#pragma warning disable CA1031 // Do not catch general exception types. + +using System; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using Emby.Dlna.PlayTo; +using Emby.Dlna.Ssdp; +using Jellyfin.Networking.Configuration; +using Jellyfin.Networking.Extensions; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Globalization; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Rssdp; +using Rssdp.Infrastructure; + +namespace Emby.Dlna.Main; + +/// +/// A that manages a DLNA server. +/// +public sealed class DlnaHost : IHostedService, IDisposable +{ + private readonly ILogger _logger; + private readonly IServerConfigurationManager _config; + private readonly IServerApplicationHost _appHost; + private readonly ISessionManager _sessionManager; + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILibraryManager _libraryManager; + private readonly IUserManager _userManager; + private readonly IDlnaManager _dlnaManager; + private readonly IImageProcessor _imageProcessor; + private readonly IUserDataManager _userDataManager; + private readonly ILocalizationManager _localization; + private readonly IMediaSourceManager _mediaSourceManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly IDeviceDiscovery _deviceDiscovery; + private readonly ISsdpCommunicationsServer _communicationsServer; + private readonly INetworkManager _networkManager; + private readonly object _syncLock = new(); + + private SsdpDevicePublisher? _publisher; + private PlayToManager? _manager; + private bool _disposed; + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + public DlnaHost( + IServerConfigurationManager config, + ILoggerFactory loggerFactory, + IServerApplicationHost appHost, + ISessionManager sessionManager, + IHttpClientFactory httpClientFactory, + ILibraryManager libraryManager, + IUserManager userManager, + IDlnaManager dlnaManager, + IImageProcessor imageProcessor, + IUserDataManager userDataManager, + ILocalizationManager localizationManager, + IMediaSourceManager mediaSourceManager, + IDeviceDiscovery deviceDiscovery, + IMediaEncoder mediaEncoder, + ISsdpCommunicationsServer communicationsServer, + INetworkManager networkManager) + { + _config = config; + _appHost = appHost; + _sessionManager = sessionManager; + _httpClientFactory = httpClientFactory; + _libraryManager = libraryManager; + _userManager = userManager; + _dlnaManager = dlnaManager; + _imageProcessor = imageProcessor; + _userDataManager = userDataManager; + _localization = localizationManager; + _mediaSourceManager = mediaSourceManager; + _deviceDiscovery = deviceDiscovery; + _mediaEncoder = mediaEncoder; + _communicationsServer = communicationsServer; + _networkManager = networkManager; + _logger = loggerFactory.CreateLogger(); + } + + /// + public async Task StartAsync(CancellationToken cancellationToken) + { + var netConfig = _config.GetConfiguration(NetworkConfigurationStore.StoreKey); + if (_appHost.ListenWithHttps && netConfig.RequireHttps) + { + if (_config.GetDlnaConfiguration().EnableServer) + { + _logger.LogError("The DLNA specification does not support HTTPS."); + } + + // No use starting as dlna won't work, as we're running purely on HTTPS. + return; + } + + await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false); + ReloadComponents(); + + _config.NamedConfigurationUpdated += OnNamedConfigurationUpdated; + } + + /// + public Task StopAsync(CancellationToken cancellationToken) + { + Stop(); + + return Task.CompletedTask; + } + + /// + public void Dispose() + { + if (!_disposed) + { + Stop(); + _disposed = true; + } + } + + private void OnNamedConfigurationUpdated(object? sender, ConfigurationUpdateEventArgs e) + { + if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase)) + { + ReloadComponents(); + } + } + + private void ReloadComponents() + { + var options = _config.GetDlnaConfiguration(); + StartDeviceDiscovery(); + + if (options.EnableServer) + { + StartDevicePublisher(options); + } + else + { + DisposeDevicePublisher(); + } + + if (options.EnablePlayTo) + { + StartPlayToManager(); + } + else + { + DisposePlayToManager(); + } + } + + private static string CreateUuid(string text) + { + if (!Guid.TryParse(text, out var guid)) + { + guid = text.GetMD5(); + } + + return guid.ToString("D", CultureInfo.InvariantCulture); + } + + private static void SetProperties(SsdpDevice device, string fullDeviceType) + { + var serviceParts = fullDeviceType + .Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase) + .Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase) + .Split(':'); + + device.DeviceTypeNamespace = serviceParts[0].Replace('.', '-'); + device.DeviceClass = serviceParts[1]; + device.DeviceType = serviceParts[2]; + } + + private void StartDeviceDiscovery() + { + try + { + ((DeviceDiscovery)_deviceDiscovery).Start(_communicationsServer); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error starting device discovery"); + } + } + + private void StartDevicePublisher(Configuration.DlnaOptions options) + { + if (_publisher is not null) + { + return; + } + + try + { + _publisher = new SsdpDevicePublisher( + _communicationsServer, + Environment.OSVersion.Platform.ToString(), + // Can not use VersionString here since that includes OS and version + Environment.OSVersion.Version.ToString(), + _config.GetDlnaConfiguration().SendOnlyMatchedHost) + { + LogFunction = msg => _logger.LogDebug("{Msg}", msg), + SupportPnpRootDevice = false + }; + + RegisterServerEndpoints(); + + if (options.BlastAliveMessages) + { + _publisher.StartSendingAliveNotifications(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds)); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error registering endpoint"); + } + } + + private void RegisterServerEndpoints() + { + var udn = CreateUuid(_appHost.SystemId); + var descriptorUri = "/dlna/" + udn + "/description.xml"; + + // Only get bind addresses in LAN + // IPv6 is currently unsupported + var validInterfaces = _networkManager.GetInternalBindAddresses() + .Where(x => x.AddressFamily != AddressFamily.InterNetworkV6) + .ToList(); + + if (validInterfaces.Count == 0) + { + // No interfaces returned, fall back to loopback + validInterfaces = _networkManager.GetLoopbacks().ToList(); + } + + foreach (var intf in validInterfaces) + { + var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; + + _logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, intf.Address); + + var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(intf.Address, false) + descriptorUri); + + var device = new SsdpRootDevice + { + CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info. + Location = uri.Uri, // Must point to the URL that serves your devices UPnP description document. + Address = intf.Address, + PrefixLength = NetworkExtensions.MaskToCidr(intf.Subnet.Prefix), + FriendlyName = "Jellyfin", + Manufacturer = "Jellyfin", + ModelName = "Jellyfin Server", + Uuid = udn + // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc. + }; + + SetProperties(device, fullService); + _publisher!.AddDevice(device); + + var embeddedDevices = new[] + { + "urn:schemas-upnp-org:service:ContentDirectory:1", + "urn:schemas-upnp-org:service:ConnectionManager:1", + // "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1" + }; + + foreach (var subDevice in embeddedDevices) + { + var embeddedDevice = new SsdpEmbeddedDevice + { + FriendlyName = device.FriendlyName, + Manufacturer = device.Manufacturer, + ModelName = device.ModelName, + Uuid = udn + // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc. + }; + + SetProperties(embeddedDevice, subDevice); + device.AddDevice(embeddedDevice); + } + } + } + + private void StartPlayToManager() + { + lock (_syncLock) + { + if (_manager is not null) + { + return; + } + + try + { + _manager = new PlayToManager( + _logger, + _sessionManager, + _libraryManager, + _userManager, + _dlnaManager, + _appHost, + _imageProcessor, + _deviceDiscovery, + _httpClientFactory, + _userDataManager, + _localization, + _mediaSourceManager, + _mediaEncoder); + + _manager.Start(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error starting PlayTo manager"); + } + } + } + + private void DisposePlayToManager() + { + lock (_syncLock) + { + if (_manager is not null) + { + try + { + _logger.LogInformation("Disposing PlayToManager"); + _manager.Dispose(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error disposing PlayTo manager"); + } + + _manager = null; + } + } + } + + private void DisposeDevicePublisher() + { + if (_publisher is not null) + { + _logger.LogInformation("Disposing SsdpDevicePublisher"); + _publisher.Dispose(); + _publisher = null; + } + } + + private void Stop() + { + DisposeDevicePublisher(); + DisposePlayToManager(); + } +} diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index c9bf7f085e..a1f1cd6490 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -866,7 +866,7 @@ namespace Emby.Server.Implementations yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly; // Dlna - yield return typeof(DlnaEntryPoint).Assembly; + yield return typeof(DlnaHost).Assembly; // Local metadata yield return typeof(BoxSetXmlSaver).Assembly; diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 20793ee394..db82a2900a 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -12,10 +12,11 @@ using System.Threading.Tasks; using Emby.Server.Implementations.Library; using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json.Converters; -using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.Plugins; @@ -37,7 +38,7 @@ namespace Emby.Server.Implementations.Plugins private readonly List _assemblyLoadContexts; private readonly JsonSerializerOptions _jsonOptions; private readonly ILogger _logger; - private readonly IApplicationHost _appHost; + private readonly IServerApplicationHost _appHost; private readonly ServerConfiguration _config; private readonly List _plugins; private readonly Version _minimumVersion; @@ -48,13 +49,13 @@ namespace Emby.Server.Implementations.Plugins /// Initializes a new instance of the class. /// /// The . - /// The . + /// The . /// The . /// The plugin path. /// The application version. public PluginManager( ILogger logger, - IApplicationHost appHost, + IServerApplicationHost appHost, ServerConfiguration config, string pluginsPath, Version appVersion) @@ -222,7 +223,7 @@ namespace Emby.Server.Implementations.Plugins try { var instance = (IPluginServiceRegistrator?)Activator.CreateInstance(pluginServiceRegistrator); - instance?.RegisterServices(serviceCollection); + instance?.RegisterServices(serviceCollection, _appHost); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) diff --git a/MediaBrowser.Common/Plugins/IPluginServiceRegistrator.cs b/MediaBrowser.Common/Plugins/IPluginServiceRegistrator.cs deleted file mode 100644 index 3afe874c52..0000000000 --- a/MediaBrowser.Common/Plugins/IPluginServiceRegistrator.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MediaBrowser.Common.Plugins -{ - using Microsoft.Extensions.DependencyInjection; - - /// - /// Defines the . - /// - public interface IPluginServiceRegistrator - { - /// - /// Registers the plugin's services with the service collection. - /// - /// - /// This interface is only used for service registration and requires a parameterless constructor. - /// - /// The service collection. - void RegisterServices(IServiceCollection serviceCollection); - } -} diff --git a/MediaBrowser.Controller/Plugins/IPluginServiceRegistrator.cs b/MediaBrowser.Controller/Plugins/IPluginServiceRegistrator.cs new file mode 100644 index 0000000000..8b62f38085 --- /dev/null +++ b/MediaBrowser.Controller/Plugins/IPluginServiceRegistrator.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace MediaBrowser.Controller.Plugins; + +/// +/// Defines the . +/// +/// +/// This interface is only used for service registration and requires a parameterless constructor. +/// +public interface IPluginServiceRegistrator +{ + /// + /// Registers the plugin's services with the service collection. + /// + /// The service collection. + /// The server application host. + void RegisterServices(IServiceCollection serviceCollection, IServerApplicationHost applicationHost); +} diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs index 1c87d11f18..a078eff77c 100644 --- a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs @@ -8,9 +8,9 @@ using Jellyfin.Server.Helpers; using MediaBrowser.Common; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Serilog; @@ -39,9 +39,9 @@ namespace Jellyfin.Server.Integration.Tests } /// - protected override IWebHostBuilder CreateWebHostBuilder() + protected override IHostBuilder CreateHostBuilder() { - return new WebHostBuilder(); + return new HostBuilder(); } /// @@ -95,18 +95,17 @@ namespace Jellyfin.Server.Integration.Tests } /// - protected override TestServer CreateServer(IWebHostBuilder builder) + protected override IHost CreateHost(IHostBuilder builder) { - // Create the test server using the base implementation - var testServer = base.CreateServer(builder); - - // Finish initializing the app host - var appHost = (TestAppHost)testServer.Services.GetRequiredService(); - appHost.ServiceProvider = testServer.Services; + var host = builder.Build(); + var appHost = (TestAppHost)host.Services.GetRequiredService(); + appHost.ServiceProvider = host.Services; appHost.InitializeServices().GetAwaiter().GetResult(); + host.Start(); + appHost.RunStartupTasksAsync().GetAwaiter().GetResult(); - return testServer; + return host; } ///