< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.ApplicationHost
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/ApplicationHost.cs
Line coverage
74%
Covered lines: 295
Uncovered lines: 101
Coverable lines: 396
Total lines: 1076
Line coverage: 74.4%
Branch coverage
45%
Covered branches: 61
Total branches: 134
Branch coverage: 45.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 AM Line coverage: 74.6% (282/378) Branch coverage: 49.2% (63/128) Total lines: 10385/20/2026 - 12:15:44 AM Line coverage: 74.6% (282/378) Branch coverage: 45.3% (58/128) Total lines: 10386/8/2026 - 12:16:15 AM Line coverage: 74.8% (285/381) Branch coverage: 45.3% (58/128) Total lines: 10426/28/2026 - 12:15:35 AM Line coverage: 75% (289/385) Branch coverage: 45.3% (58/128) Total lines: 10537/22/2026 - 12:16:22 AM Line coverage: 74.6% (295/395) Branch coverage: 45.4% (60/132) Total lines: 10758/3/2026 - 12:16:46 AM Line coverage: 74.4% (295/396) Branch coverage: 44.7% (60/134) Total lines: 107610/29/2026 - 12:17:11 AM Line coverage: 74.4% (295/396) Branch coverage: 45.5% (61/134) Total lines: 1076 5/16/2026 - 12:15:55 AM Line coverage: 74.6% (282/378) Branch coverage: 49.2% (63/128) Total lines: 10385/20/2026 - 12:15:44 AM Line coverage: 74.6% (282/378) Branch coverage: 45.3% (58/128) Total lines: 10386/8/2026 - 12:16:15 AM Line coverage: 74.8% (285/381) Branch coverage: 45.3% (58/128) Total lines: 10426/28/2026 - 12:15:35 AM Line coverage: 75% (289/385) Branch coverage: 45.3% (58/128) Total lines: 10537/22/2026 - 12:16:22 AM Line coverage: 74.6% (295/395) Branch coverage: 45.4% (60/132) Total lines: 10758/3/2026 - 12:16:46 AM Line coverage: 74.4% (295/396) Branch coverage: 44.7% (60/134) Total lines: 107610/29/2026 - 12:17:11 AM Line coverage: 74.4% (295/396) Branch coverage: 45.5% (61/134) Total lines: 1076

Coverage delta

Coverage delta 4 -4

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_PublishedServerUrl()100%210%
get_ApplicationUserAgentAddress()100%11100%
get_SystemId()100%11100%
get_Name()100%11100%
get_ListenWithHttps()0%620%
get_FriendlyName()0%620%
ExpandVirtualPath(...)50%2280%
ReverseVirtualPath(...)100%11100%
CreateInstanceSafe(...)62.5%15852.63%
Resolve()100%11100%
GetExportTypes()100%44100%
GetExports(...)83.33%6688.88%
GetExports(...)100%66100%
RunStartupTasksAsync()50%2291.66%
EnsureStartupWizardIntegrity()50%5455.55%
Init(...)50%4481.25%
RegisterServices(...)100%11100%
InitializeServices()100%11100%
GetCertificate(...)12.5%48814.28%
SetStaticProperties()100%11100%
FindParts()100%22100%
DiscoverTypes()100%11100%
GetTypes()100%341246.66%
OnConfigurationUpdated(...)42.85%341453.33%
ValidateSslCertificate(...)33.33%15636.36%
NotifyPendingRestart()0%620%
GetComposablePartAssemblies()75%4493.75%
GetSmartApiUrl(...)0%620%
GetSmartApiUrl(...)0%210140%
GetSmartApiUrl(...)0%620%
GetApiUrlForLocalAccess(...)0%2040%
GetLocalApiUrl(...)0%156120%
GetApiPluginAssemblies()100%22100%
Dispose()100%11100%
Dispose(...)70%111077.77%

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/ApplicationHost.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4
 5using System;
 6using System.Collections.Concurrent;
 7using System.Collections.Generic;
 8using System.Diagnostics;
 9using System.Globalization;
 10using System.IO;
 11using System.Linq;
 12using System.Net;
 13using System.Reflection;
 14using System.Security.Cryptography.X509Certificates;
 15using System.Threading.Tasks;
 16using Emby.Naming.Common;
 17using Emby.Naming.Video;
 18using Emby.Photos;
 19using Emby.Server.Implementations.Chapters;
 20using Emby.Server.Implementations.Collections;
 21using Emby.Server.Implementations.Configuration;
 22using Emby.Server.Implementations.Cryptography;
 23using Emby.Server.Implementations.Data;
 24using Emby.Server.Implementations.Devices;
 25using Emby.Server.Implementations.Dto;
 26using Emby.Server.Implementations.HttpServer.Security;
 27using Emby.Server.Implementations.IO;
 28using Emby.Server.Implementations.Library;
 29using Emby.Server.Implementations.Library.Search;
 30using Emby.Server.Implementations.Library.SimilarItems;
 31using Emby.Server.Implementations.Localization;
 32using Emby.Server.Implementations.Playlists;
 33using Emby.Server.Implementations.Plugins;
 34using Emby.Server.Implementations.QuickConnect;
 35using Emby.Server.Implementations.ScheduledTasks;
 36using Emby.Server.Implementations.Serialization;
 37using Emby.Server.Implementations.Session;
 38using Emby.Server.Implementations.SyncPlay;
 39using Emby.Server.Implementations.TV;
 40using Emby.Server.Implementations.Updates;
 41using Jellyfin.Api.Helpers;
 42using Jellyfin.Data;
 43using Jellyfin.Database.Implementations.Enums;
 44using Jellyfin.Drawing;
 45using Jellyfin.MediaEncoding.Hls.Playlist;
 46using Jellyfin.Networking.Manager;
 47using Jellyfin.Networking.Udp;
 48using Jellyfin.Server.Implementations.FullSystemBackup;
 49using Jellyfin.Server.Implementations.Item;
 50using Jellyfin.Server.Implementations.MediaSegments;
 51using Jellyfin.Server.Implementations.SystemBackupService;
 52using MediaBrowser.Common;
 53using MediaBrowser.Common.Configuration;
 54using MediaBrowser.Common.Events;
 55using MediaBrowser.Common.Net;
 56using MediaBrowser.Common.Plugins;
 57using MediaBrowser.Common.Updates;
 58using MediaBrowser.Controller;
 59using MediaBrowser.Controller.Channels;
 60using MediaBrowser.Controller.Chapters;
 61using MediaBrowser.Controller.ClientEvent;
 62using MediaBrowser.Controller.Collections;
 63using MediaBrowser.Controller.Configuration;
 64using MediaBrowser.Controller.Drawing;
 65using MediaBrowser.Controller.Dto;
 66using MediaBrowser.Controller.Entities;
 67using MediaBrowser.Controller.Entities.TV;
 68using MediaBrowser.Controller.IO;
 69using MediaBrowser.Controller.Library;
 70using MediaBrowser.Controller.LibraryTaskScheduler;
 71using MediaBrowser.Controller.LiveTv;
 72using MediaBrowser.Controller.Lyrics;
 73using MediaBrowser.Controller.MediaEncoding;
 74using MediaBrowser.Controller.MediaSegments;
 75using MediaBrowser.Controller.Net;
 76using MediaBrowser.Controller.Persistence;
 77using MediaBrowser.Controller.Playlists;
 78using MediaBrowser.Controller.Providers;
 79using MediaBrowser.Controller.QuickConnect;
 80using MediaBrowser.Controller.Resolvers;
 81using MediaBrowser.Controller.Session;
 82using MediaBrowser.Controller.Sorting;
 83using MediaBrowser.Controller.Subtitles;
 84using MediaBrowser.Controller.SyncPlay;
 85using MediaBrowser.Controller.TV;
 86using MediaBrowser.LocalMetadata.Savers;
 87using MediaBrowser.MediaEncoding.BdInfo;
 88using MediaBrowser.MediaEncoding.Subtitles;
 89using MediaBrowser.MediaEncoding.Transcoding;
 90using MediaBrowser.Model.Cryptography;
 91using MediaBrowser.Model.Globalization;
 92using MediaBrowser.Model.IO;
 93using MediaBrowser.Model.MediaInfo;
 94using MediaBrowser.Model.Net;
 95using MediaBrowser.Model.Serialization;
 96using MediaBrowser.Model.System;
 97using MediaBrowser.Model.Tasks;
 98using MediaBrowser.Providers.Books;
 99using MediaBrowser.Providers.Books.ComicBookInfo;
 100using MediaBrowser.Providers.Books.ComicInfo;
 101using MediaBrowser.Providers.Lyric;
 102using MediaBrowser.Providers.Manager;
 103using MediaBrowser.Providers.Plugins.ListenBrainz;
 104using MediaBrowser.Providers.Plugins.ListenBrainz.Api;
 105using MediaBrowser.Providers.Plugins.Tmdb;
 106using MediaBrowser.Providers.Plugins.Tmdb.Movies;
 107using MediaBrowser.Providers.Plugins.Tmdb.TV;
 108using MediaBrowser.Providers.Subtitles;
 109using MediaBrowser.XbmcMetadata.Providers;
 110using Microsoft.AspNetCore.Http;
 111using Microsoft.AspNetCore.Mvc;
 112using Microsoft.Extensions.Configuration;
 113using Microsoft.Extensions.DependencyInjection;
 114using Microsoft.Extensions.Logging;
 115using Prometheus.DotNetRuntime;
 116using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
 117using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 118using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager;
 119
 120namespace Emby.Server.Implementations
 121{
 122    /// <summary>
 123    /// Class CompositionRoot.
 124    /// </summary>
 125    public abstract class ApplicationHost : IServerApplicationHost, IDisposable
 126    {
 127        /// <summary>
 128        /// The disposable parts.
 129        /// </summary>
 28130        private readonly ConcurrentBag<IDisposable> _disposableParts = new();
 131        private readonly DeviceId _deviceId;
 132
 133        private readonly IConfiguration _startupConfig;
 134        private readonly IXmlSerializer _xmlSerializer;
 135        private readonly IStartupOptions _startupOptions;
 136        private readonly PluginManager _pluginManager;
 137
 138        private List<Type> _creatingInstances;
 139
 140        /// <summary>
 141        /// Gets or sets all concrete types.
 142        /// </summary>
 143        /// <value>All concrete types.</value>
 144        private Type[] _allConcreteTypes;
 145
 146        private bool _disposed;
 147
 148        /// <summary>
 149        /// Initializes a new instance of the <see cref="ApplicationHost"/> class.
 150        /// </summary>
 151        /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param>
 152        /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
 153        /// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
 154        /// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param>
 155        protected ApplicationHost(
 156            IServerApplicationPaths applicationPaths,
 157            ILoggerFactory loggerFactory,
 158            IStartupOptions options,
 159            IConfiguration startupConfig)
 160        {
 161            ApplicationPaths = applicationPaths;
 162            LoggerFactory = loggerFactory;
 28163            _startupOptions = options;
 28164            _startupConfig = startupConfig;
 165
 28166            Logger = LoggerFactory.CreateLogger<ApplicationHost>();
 28167            _deviceId = new DeviceId(ApplicationPaths, LoggerFactory.CreateLogger<DeviceId>());
 168
 28169            ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
 28170            ApplicationVersionString = ApplicationVersion.ToString(3);
 28171            ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString;
 172
 28173            _xmlSerializer = new MyXmlSerializer();
 28174            ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer);
 28175            _pluginManager = new PluginManager(
 28176                LoggerFactory.CreateLogger<PluginManager>(),
 28177                this,
 28178                ConfigurationManager.Configuration,
 28179                ApplicationPaths.PluginsPath,
 28180                ApplicationVersion);
 28181        }
 182
 183        /// <summary>
 184        /// Occurs when [has pending restart changed].
 185        /// </summary>
 186        public event EventHandler HasPendingRestartChanged;
 187
 188        /// <summary>
 189        /// Gets the value of the PublishedServerUrl setting.
 190        /// </summary>
 0191        private string PublishedServerUrl => _startupConfig[AddressOverrideKey];
 192
 193        public bool CoreStartupHasCompleted { get; private set; }
 194
 195        /// <summary>
 196        /// Gets the <see cref="INetworkManager"/> singleton instance.
 197        /// </summary>
 198        public INetworkManager NetManager { get; private set; }
 199
 200        /// <inheritdoc />
 201        public bool HasPendingRestart { get; private set; }
 202
 203        /// <inheritdoc />
 204        public bool ShouldRestart { get; set; }
 205
 206        /// <summary>
 207        /// Gets the logger.
 208        /// </summary>
 209        protected ILogger<ApplicationHost> Logger { get; }
 210
 211        /// <summary>
 212        /// Gets the logger factory.
 213        /// </summary>
 214        protected ILoggerFactory LoggerFactory { get; }
 215
 216        /// <summary>
 217        /// Gets the application paths.
 218        /// </summary>
 219        /// <value>The application paths.</value>
 220        protected IServerApplicationPaths ApplicationPaths { get; }
 221
 222        /// <summary>
 223        /// Gets the configuration manager.
 224        /// </summary>
 225        /// <value>The configuration manager.</value>
 226        public ServerConfigurationManager ConfigurationManager { get; }
 227
 228        /// <summary>
 229        /// Gets or sets the service provider.
 230        /// </summary>
 231        public IServiceProvider ServiceProvider { get; set; }
 232
 233        /// <summary>
 234        /// Gets the http port for the webhost.
 235        /// </summary>
 236        public int HttpPort { get; private set; }
 237
 238        /// <summary>
 239        /// Gets the https port for the webhost.
 240        /// </summary>
 241        public int HttpsPort { get; private set; }
 242
 243        /// <inheritdoc />
 244        public Version ApplicationVersion { get; }
 245
 246        /// <inheritdoc />
 247        public string ApplicationVersionString { get; }
 248
 249        /// <summary>
 250        /// Gets the current application user agent.
 251        /// </summary>
 252        /// <value>The application user agent.</value>
 253        public string ApplicationUserAgent { get; }
 254
 255        /// <summary>
 256        /// Gets the email address for use within a comment section of a user agent field.
 257        /// Presently used to provide contact information to MusicBrainz service.
 258        /// </summary>
 27259        public string ApplicationUserAgentAddress => "team@jellyfin.org";
 260
 261        /// <summary>
 262        /// Gets the current application name.
 263        /// </summary>
 264        /// <value>The application name.</value>
 28265        public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Locat
 266
 132267        public string SystemId => _deviceId.Value;
 268
 269        /// <inheritdoc/>
 83270        public string Name => ApplicationProductName;
 271
 272        private string CertificatePath { get; set; }
 273
 274        public X509Certificate2 Certificate { get; private set; }
 275
 276        /// <inheritdoc/>
 0277        public bool ListenWithHttps => Certificate is not null && ConfigurationManager.GetNetworkConfiguration().EnableH
 278
 279        public string FriendlyName =>
 0280            string.IsNullOrEmpty(ConfigurationManager.Configuration.ServerName)
 0281                ? Environment.MachineName
 0282                : ConfigurationManager.Configuration.ServerName;
 283
 284        public string RestoreBackupPath { get; set; }
 285
 286        public string ExpandVirtualPath(string path)
 287        {
 163288            if (path is null)
 289            {
 0290                return null;
 291            }
 292
 163293            var appPaths = ApplicationPaths;
 294
 163295            return path.Replace(appPaths.VirtualDataPath, appPaths.DataPath, StringComparison.OrdinalIgnoreCase)
 163296                .Replace(appPaths.VirtualInternalMetadataPath, appPaths.InternalMetadataPath, StringComparison.OrdinalIg
 297        }
 298
 299        public string ReverseVirtualPath(string path)
 300        {
 159301            var appPaths = ApplicationPaths;
 302
 159303            return path.Replace(appPaths.DataPath, appPaths.VirtualDataPath, StringComparison.OrdinalIgnoreCase)
 159304                .Replace(appPaths.InternalMetadataPath, appPaths.VirtualInternalMetadataPath, StringComparison.OrdinalIg
 305        }
 306
 307        /// <summary>
 308        /// Creates the instance safe.
 309        /// </summary>
 310        /// <param name="type">The type.</param>
 311        /// <returns>System.Object.</returns>
 312        protected object CreateInstanceSafe(Type type)
 313        {
 6055314            _creatingInstances ??= new List<Type>();
 315
 6055316            if (_creatingInstances.Contains(type))
 317            {
 0318                Logger.LogError("DI Loop detected in the attempted creation of {Type}", type.FullName);
 0319                foreach (var entry in _creatingInstances)
 320                {
 0321                    Logger.LogError("Called from: {TypeName}", entry.FullName);
 322                }
 323
 0324                _pluginManager.FailPlugin(type.Assembly);
 325
 0326                throw new TypeLoadException("DI Loop detected");
 327            }
 328
 329            try
 330            {
 6055331                _creatingInstances.Add(type);
 6055332                Logger.LogDebug("Creating instance of {Type}", type);
 6055333                return ServiceProvider is null
 6055334                    ? Activator.CreateInstance(type)
 6055335                    : ActivatorUtilities.CreateInstance(ServiceProvider, type);
 336            }
 0337            catch (Exception ex)
 338            {
 0339                Logger.LogError(ex, "Error creating {Type}", type);
 340                // If this is a plugin fail it.
 0341                _pluginManager.FailPlugin(type.Assembly);
 0342                return null;
 343            }
 344            finally
 345            {
 6055346                _creatingInstances.Remove(type);
 6055347            }
 6055348        }
 349
 350        /// <summary>
 351        /// Resolves this instance.
 352        /// </summary>
 353        /// <typeparam name="T">The type.</typeparam>
 354        /// <returns>``0.</returns>
 729355        public T Resolve<T>() => ServiceProvider.GetService<T>();
 356
 357        /// <inheritdoc/>
 358        public IEnumerable<Type> GetExportTypes<T>()
 359        {
 544360            var currentType = typeof(T);
 544361            var numberOfConcreteTypes = _allConcreteTypes.Length;
 1156544362            for (var i = 0; i < numberOfConcreteTypes; i++)
 363            {
 577728364                var type = _allConcreteTypes[i];
 577728365                if (currentType.IsAssignableFrom(type))
 366                {
 6327367                    yield return type;
 368                }
 369            }
 544370        }
 371
 372        /// <inheritdoc />
 373        public IReadOnlyCollection<T> GetExports<T>(bool manageLifetime = true)
 374        {
 375            // Convert to list so this isn't executed for each iteration
 433376            var parts = GetExportTypes<T>()
 433377                .Select(CreateInstanceSafe)
 433378                .Where(i => i is not null)
 433379                .Cast<T>()
 433380                .ToList();
 381
 433382            if (manageLifetime)
 383            {
 812384                foreach (var part in parts.OfType<IDisposable>())
 385                {
 0386                    _disposableParts.Add(part);
 387                }
 388            }
 389
 433390            return parts;
 391        }
 392
 393        /// <inheritdoc />
 394        public IReadOnlyCollection<T> GetExports<T>(CreationDelegateFactory defaultFunc, bool manageLifetime = true)
 395        {
 396            // Convert to list so this isn't executed for each iteration
 27397            var parts = GetExportTypes<T>()
 27398                .Select(i => defaultFunc(i))
 27399                .Where(i => i is not null)
 27400                .Cast<T>()
 27401                .ToList();
 402
 27403            if (manageLifetime)
 404            {
 108405                foreach (var part in parts.OfType<IDisposable>())
 406                {
 27407                    _disposableParts.Add(part);
 408                }
 409            }
 410
 27411            return parts;
 412        }
 413
 414        /// <summary>
 415        /// Runs the startup tasks.
 416        /// </summary>
 417        /// <returns><see cref="Task" />.</returns>
 418        public Task RunStartupTasksAsync()
 419        {
 27420            Logger.LogInformation("Running startup tasks");
 421
 27422            EnsureStartupWizardIntegrity();
 423
 27424            Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false));
 425
 27426            ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
 27427            ConfigurationManager.NamedConfigurationUpdated += OnConfigurationUpdated;
 428
 27429            var ffmpegValid = Resolve<IMediaEncoder>().SetFFmpegPath();
 430
 27431            if (!ffmpegValid)
 432            {
 0433                throw new FfmpegException("Failed to find valid ffmpeg");
 434            }
 435
 27436            Logger.LogInformation("ServerId: {ServerId}", SystemId);
 27437            Logger.LogInformation("Core startup complete");
 27438            CoreStartupHasCompleted = true;
 439
 27440            return Task.CompletedTask;
 441        }
 442
 443        private void EnsureStartupWizardIntegrity()
 444        {
 27445            if (ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted)
 446            {
 0447                return;
 448            }
 449
 27450            var hasConfiguredAdministrator = Resolve<IUserManager>().GetUsers()
 27451                .Any(user => user.HasPermission(PermissionKind.IsAdministrator) && !string.IsNullOrEmpty(user.Password))
 452
 27453            if (hasConfiguredAdministrator)
 454            {
 0455                Logger.LogWarning("The startup wizard is marked incomplete but a configured administrator already exists
 0456                ConfigurationManager.Configuration.IsStartupWizardCompleted = true;
 0457                ConfigurationManager.SaveConfiguration();
 458            }
 27459        }
 460
 461        /// <inheritdoc/>
 462        public void Init(IServiceCollection serviceCollection)
 463        {
 28464            DiscoverTypes();
 465
 28466            ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
 467
 28468            NetManager = new NetworkManager(ConfigurationManager, _startupConfig, LoggerFactory.CreateLogger<NetworkMana
 469
 470            // Initialize runtime stat collection
 28471            if (ConfigurationManager.Configuration.EnableMetrics)
 472            {
 0473                _disposableParts.Add(DotNetRuntimeStatsBuilder.Default().StartCollecting());
 474            }
 475
 28476            var networkConfiguration = ConfigurationManager.GetNetworkConfiguration();
 28477            HttpPort = networkConfiguration.InternalHttpPort;
 28478            HttpsPort = networkConfiguration.InternalHttpsPort;
 479
 480            // Safeguard against invalid configuration
 28481            if (HttpPort == HttpsPort)
 482            {
 0483                HttpPort = NetworkConfiguration.DefaultHttpPort;
 0484                HttpsPort = NetworkConfiguration.DefaultHttpsPort;
 485            }
 486
 28487            CertificatePath = networkConfiguration.CertificatePath;
 28488            Certificate = GetCertificate(CertificatePath, networkConfiguration.CertificatePassword);
 489
 28490            RegisterServices(serviceCollection);
 491
 28492            _pluginManager.RegisterServices(serviceCollection);
 28493        }
 494
 495        /// <summary>
 496        /// Registers services/resources with the service collection that will be available via DI.
 497        /// </summary>
 498        /// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
 499        protected virtual void RegisterServices(IServiceCollection serviceCollection)
 500        {
 28501            serviceCollection.AddSingleton(_startupOptions);
 502
 28503            serviceCollection.AddMemoryCache();
 504
 28505            serviceCollection.AddSingleton<IServerConfigurationManager>(ConfigurationManager);
 28506            serviceCollection.AddSingleton<IConfigurationManager>(ConfigurationManager);
 28507            serviceCollection.AddSingleton<IApplicationHost>(this);
 28508            serviceCollection.AddSingleton<IPluginManager>(_pluginManager);
 28509            serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
 28510            serviceCollection.AddSingleton<IBackupService, BackupService>();
 511
 28512            serviceCollection.AddSingleton<IFileSystem, ManagedFileSystem>();
 28513            serviceCollection.AddSingleton<IShortcutHandler, MbLinkShortcutHandler>();
 514
 28515            serviceCollection.AddScoped<ISystemManager, SystemManager>();
 516
 28517            serviceCollection.AddSingleton<TmdbClientManager>();
 28518            serviceCollection.AddSingleton<TmdbMovieSimilarProvider>();
 28519            serviceCollection.AddSingleton<TmdbSeriesSimilarProvider>();
 520
 28521            serviceCollection.AddSingleton<ListenBrainzLabsClient>();
 28522            serviceCollection.AddSingleton<ListenBrainzSimilarArtistProvider>();
 523
 524            // register the generic local metadata provider for comic files
 28525            serviceCollection.AddSingleton<ComicProvider>();
 526
 527            // register the actual implementations of the local metadata provider for comic files
 28528            serviceCollection.AddSingleton<IComicProvider, ComicBookInfoProvider>();
 28529            serviceCollection.AddSingleton<IComicProvider, ExternalComicInfoProvider>();
 28530            serviceCollection.AddSingleton<IComicProvider, InternalComicInfoProvider>();
 531
 28532            serviceCollection.AddSingleton(NetManager);
 533
 28534            serviceCollection.AddSingleton<ITaskManager, TaskManager>();
 535
 28536            serviceCollection.AddSingleton(_xmlSerializer);
 537
 28538            serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
 539
 28540            serviceCollection.AddSingleton<ISocketFactory, SocketFactory>();
 541
 28542            serviceCollection.AddSingleton<IInstallationManager, InstallationManager>();
 543
 28544            serviceCollection.AddSingleton<IServerApplicationHost>(this);
 28545            serviceCollection.AddSingleton(ApplicationPaths);
 546
 28547            serviceCollection.AddSingleton<ILocalizationManager, LocalizationManager>();
 548
 28549            serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
 550
 28551            serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
 552
 28553            serviceCollection.AddSingleton<BaseItemRepository>();
 28554            serviceCollection.AddSingleton<IItemRepository>(sp => sp.GetRequiredService<BaseItemRepository>());
 28555            serviceCollection.AddSingleton<IItemQueryHelpers>(sp => sp.GetRequiredService<BaseItemRepository>());
 28556            serviceCollection.AddSingleton<IItemPersistenceService, ItemPersistenceService>();
 28557            serviceCollection.AddSingleton<INextUpService, NextUpService>();
 28558            serviceCollection.AddSingleton<IItemCountService, ItemCountService>();
 28559            serviceCollection.AddSingleton<ILinkedChildrenService, LinkedChildrenService>();
 28560            serviceCollection.AddSingleton<IPeopleRepository, PeopleRepository>();
 28561            serviceCollection.AddSingleton<IChapterRepository, ChapterRepository>();
 28562            serviceCollection.AddSingleton<IMediaAttachmentRepository, MediaAttachmentRepository>();
 28563            serviceCollection.AddSingleton<IMediaStreamRepository, MediaStreamRepository>();
 28564            serviceCollection.AddSingleton<IKeyframeRepository, KeyframeRepository>();
 28565            serviceCollection.AddSingleton<IItemTypeLookup, ItemTypeLookup>();
 566
 28567            serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
 28568            serviceCollection.AddSingleton<EncodingHelper>();
 28569            serviceCollection.AddSingleton<IPathManager, PathManager>();
 28570            serviceCollection.AddSingleton<IExternalDataManager, ExternalDataManager>();
 571
 572            // TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
 28573            serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMon
 28574            serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderM
 28575            serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewM
 28576            serviceCollection.AddTransient(provider => new Lazy<IExternalDataManager>(provider.GetRequiredService<IExter
 28577            serviceCollection.AddSingleton<ILibraryManager, LibraryManager>();
 28578            serviceCollection.AddSingleton<NamingOptions>();
 28579            serviceCollection.AddSingleton<VideoListResolver>();
 580
 28581            serviceCollection.AddSingleton<IMusicManager, MusicManager>();
 582
 28583            serviceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>();
 28584            serviceCollection.AddSingleton<DotIgnoreIgnoreRule>();
 585
 28586            serviceCollection.AddSingleton<ISimilarItemsManager, SimilarItemsManager>();
 587
 28588            serviceCollection.AddSingleton<ISearchManager, SearchManager>();
 28589            serviceCollection.AddSingleton<ISearchProvider, SqlSearchProvider>();
 590
 28591            serviceCollection.AddSingleton<IWebSocketManager, WebSocketManager>();
 592
 28593            serviceCollection.AddSingleton<IImageProcessor, ImageProcessor>();
 594
 28595            serviceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
 596
 28597            serviceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
 598
 28599            serviceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
 28600            serviceCollection.AddSingleton<ILyricManager, LyricManager>();
 601
 28602            serviceCollection.AddSingleton<IProviderManager, ProviderManager>();
 603
 604            // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
 28605            serviceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManag
 28606            serviceCollection.AddSingleton<IDtoService, DtoService>();
 607
 28608            serviceCollection.AddSingleton<ISessionManager, SessionManager>();
 609
 28610            serviceCollection.AddSingleton<ICollectionManager, CollectionManager>();
 28611            serviceCollection.AddSingleton<ILimitedConcurrencyLibraryScheduler, LimitedConcurrencyLibraryScheduler>();
 612
 28613            serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
 614
 28615            serviceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>();
 616
 28617            serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
 618
 28619            serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
 620
 28621            serviceCollection.AddSingleton<IAuthService, AuthService>();
 28622            serviceCollection.AddSingleton<IQuickConnect, QuickConnectManager>();
 623
 28624            serviceCollection.AddSingleton<ISubtitleParser, SubtitleEditParser>();
 28625            serviceCollection.AddSingleton<ISubtitleEncoder, SubtitleEncoder>();
 28626            serviceCollection.AddSingleton<IKeyframeManager, KeyframeManager>();
 627
 28628            serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtrac
 629
 28630            serviceCollection.AddSingleton<ITranscodeManager, TranscodeManager>();
 28631            serviceCollection.AddScoped<MediaInfoHelper>();
 28632            serviceCollection.AddScoped<AudioHelper>();
 28633            serviceCollection.AddScoped<DynamicHlsHelper>();
 28634            serviceCollection.AddScoped<IClientEventLogger, ClientEventLogger>();
 28635            serviceCollection.AddSingleton<IDirectoryService, DirectoryService>();
 636
 28637            serviceCollection.AddSingleton<IMediaSegmentManager, MediaSegmentManager>();
 28638        }
 639
 640        /// <summary>
 641        /// Create services registered with the service container that need to be initialized at application startup.
 642        /// </summary>
 643        /// <param name="startupConfig">The configuration used to initialise the application.</param>
 644        /// <returns>A task representing the service initialization operation.</returns>
 645        public async Task InitializeServices(IConfiguration startupConfig)
 646        {
 27647            var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>();
 27648            await localizationManager.LoadAll().ConfigureAwait(false);
 649
 27650            SetStaticProperties();
 651
 27652            FindParts();
 27653        }
 654
 655        private X509Certificate2 GetCertificate(string path, string password)
 656        {
 28657            if (string.IsNullOrWhiteSpace(path))
 658            {
 28659                return null;
 660            }
 661
 662            try
 663            {
 0664                if (!File.Exists(path))
 665                {
 0666                    return null;
 667                }
 668
 669                // Don't use an empty string password
 0670                password = string.IsNullOrWhiteSpace(password) ? null : password;
 671
 0672                var localCert = X509CertificateLoader.LoadPkcs12FromFile(path, password, X509KeyStorageFlags.UserKeySet)
 0673                if (!localCert.HasPrivateKey)
 674                {
 0675                    Logger.LogError("No private key included in SSL cert {CertificateLocation}.", path);
 0676                    return null;
 677                }
 678
 0679                return localCert;
 680            }
 0681            catch (Exception ex)
 682            {
 0683                Logger.LogError(ex, "Error loading cert from {CertificateLocation}", path);
 0684                return null;
 685            }
 0686        }
 687
 688        /// <summary>
 689        /// Dirty hacks.
 690        /// </summary>
 691        private void SetStaticProperties()
 692        {
 693            // For now there's no real way to inject these properly
 27694            BaseItem.ChapterManager = Resolve<IChapterManager>();
 27695            BaseItem.ChannelManager = Resolve<IChannelManager>();
 27696            BaseItem.ConfigurationManager = ConfigurationManager;
 27697            BaseItem.FileSystem = Resolve<IFileSystem>();
 27698            BaseItem.ItemRepository = Resolve<IItemRepository>();
 27699            BaseItem.ItemCountService = Resolve<IItemCountService>();
 27700            BaseItem.LibraryManager = Resolve<ILibraryManager>();
 27701            BaseItem.LocalizationManager = Resolve<ILocalizationManager>();
 27702            BaseItem.Logger = Resolve<ILogger<BaseItem>>();
 27703            BaseItem.MediaSegmentManager = Resolve<IMediaSegmentManager>();
 27704            BaseItem.MediaSourceManager = Resolve<IMediaSourceManager>();
 27705            BaseItem.ProviderManager = Resolve<IProviderManager>();
 27706            BaseItem.UserDataManager = Resolve<IUserDataManager>();
 27707            CollectionFolder.XmlSerializer = _xmlSerializer;
 27708            CollectionFolder.ApplicationHost = this;
 27709            Folder.UserViewManager = Resolve<IUserViewManager>();
 27710            Folder.CollectionManager = Resolve<ICollectionManager>();
 27711            Folder.LimitedConcurrencyLibraryScheduler = Resolve<ILimitedConcurrencyLibraryScheduler>();
 27712            Episode.MediaEncoder = Resolve<IMediaEncoder>();
 27713            UserView.TVSeriesManager = Resolve<ITVSeriesManager>();
 27714            Video.RecordingsManager = Resolve<IRecordingsManager>();
 27715        }
 716
 717        /// <summary>
 718        /// Finds plugin components and register them with the appropriate services.
 719        /// </summary>
 720        private void FindParts()
 721        {
 27722            if (!ConfigurationManager.Configuration.IsPortAuthorized)
 723            {
 26724                ConfigurationManager.Configuration.IsPortAuthorized = true;
 26725                ConfigurationManager.SaveConfiguration();
 726            }
 727
 27728            _pluginManager.CreatePlugins();
 729
 27730            Resolve<ILibraryManager>().AddParts(
 27731                GetExports<IResolverIgnoreRule>(),
 27732                GetExports<IItemResolver>(),
 27733                GetExports<IIntroProvider>(),
 27734                GetExports<IBaseItemComparer>(),
 27735                GetExports<ILibraryPostScanTask>());
 736
 27737            Resolve<IProviderManager>().AddParts(
 27738                GetExports<IImageProvider>(),
 27739                GetExports<IMetadataService>(),
 27740                GetExports<IMetadataProvider>(),
 27741                GetExports<IMetadataSaver>(),
 27742                GetExports<IExternalId>(),
 27743                GetExports<IExternalUrlProvider>());
 744
 27745            Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
 746
 27747            Resolve<ISimilarItemsManager>().AddParts(GetExports<ISimilarItemsProvider>());
 27748            Resolve<ISearchManager>().AddParts(GetExports<ISearchProvider>());
 27749        }
 750
 751        /// <summary>
 752        /// Discovers the types.
 753        /// </summary>
 754        protected void DiscoverTypes()
 755        {
 28756            Logger.LogInformation("Loading assemblies");
 757
 28758            _allConcreteTypes = GetTypes(GetComposablePartAssemblies()).ToArray();
 28759        }
 760
 761        private IEnumerable<Type> GetTypes(IEnumerable<Assembly> assemblies)
 762        {
 952763            foreach (var ass in assemblies)
 764            {
 765                Type[] exportedTypes;
 766                try
 767                {
 448768                    exportedTypes = ass.GetExportedTypes();
 448769                }
 0770                catch (FileNotFoundException ex)
 771                {
 0772                    Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName);
 0773                    _pluginManager.FailPlugin(ass);
 0774                    continue;
 775                }
 0776                catch (TypeLoadException ex)
 777                {
 0778                    Logger.LogError(ex, "Error loading types from {Assembly}.", ass.FullName);
 0779                    _pluginManager.FailPlugin(ass);
 0780                    continue;
 781                }
 782
 86296783                foreach (Type type in exportedTypes)
 784                {
 42700785                    if (type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType)
 786                    {
 29736787                        yield return type;
 788                    }
 789                }
 790            }
 28791        }
 792
 793        /// <summary>
 794        /// Called when [configuration updated].
 795        /// </summary>
 796        /// <param name="sender">The sender.</param>
 797        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
 798        private void OnConfigurationUpdated(object sender, EventArgs e)
 799        {
 19800            var requiresRestart = false;
 19801            var networkConfiguration = ConfigurationManager.GetNetworkConfiguration();
 802
 803            // Don't do anything if these haven't been set yet
 19804            if (HttpPort != 0 && HttpsPort != 0)
 805            {
 806                // Need to restart if ports have changed
 19807                if (networkConfiguration.InternalHttpPort != HttpPort
 19808                    || networkConfiguration.InternalHttpsPort != HttpsPort)
 809                {
 0810                    if (ConfigurationManager.Configuration.IsPortAuthorized)
 811                    {
 0812                        ConfigurationManager.Configuration.IsPortAuthorized = false;
 0813                        ConfigurationManager.SaveConfiguration();
 814
 0815                        requiresRestart = true;
 816                    }
 817                }
 818            }
 819
 19820            if (ValidateSslCertificate(networkConfiguration))
 821            {
 0822                requiresRestart = true;
 823            }
 824
 19825            if (requiresRestart)
 826            {
 0827                Logger.LogInformation("App needs to be restarted due to configuration change.");
 828
 0829                NotifyPendingRestart();
 830            }
 19831        }
 832
 833        /// <summary>
 834        /// Validates the SSL certificate.
 835        /// </summary>
 836        /// <param name="networkConfig">The new configuration.</param>
 837        /// <exception cref="FileNotFoundException">The certificate path doesn't exist.</exception>
 838        private bool ValidateSslCertificate(NetworkConfiguration networkConfig)
 839        {
 19840            var newPath = networkConfig.CertificatePath;
 841
 19842            if (!string.IsNullOrWhiteSpace(newPath)
 19843                && !string.Equals(CertificatePath, newPath, StringComparison.Ordinal))
 844            {
 0845                if (File.Exists(newPath))
 846                {
 0847                    return true;
 848                }
 849
 0850                throw new FileNotFoundException(
 0851                    string.Format(
 0852                        CultureInfo.InvariantCulture,
 0853                        "Certificate file '{0}' does not exist.",
 0854                        newPath));
 855            }
 856
 19857            return false;
 858        }
 859
 860        /// <summary>
 861        /// Notifies the kernel that a change has been made that requires a restart.
 862        /// </summary>
 863        public void NotifyPendingRestart()
 864        {
 0865            Logger.LogInformation("App needs to be restarted.");
 866
 0867            var changed = !HasPendingRestart;
 868
 0869            HasPendingRestart = true;
 870
 0871            if (changed)
 872            {
 0873                EventHelper.QueueEventIfNotNull(HasPendingRestartChanged, this, EventArgs.Empty, Logger);
 874            }
 0875        }
 876
 877        /// <summary>
 878        /// Gets the composable part assemblies.
 879        /// </summary>
 880        /// <returns>IEnumerable{Assembly}.</returns>
 881        protected IEnumerable<Assembly> GetComposablePartAssemblies()
 882        {
 56883            foreach (var p in _pluginManager.LoadAssemblies())
 884            {
 0885                yield return p;
 886            }
 887
 888            // Include composable parts in the Model assembly
 28889            yield return typeof(SystemInfo).Assembly;
 890
 891            // Include composable parts in the Common assembly
 28892            yield return typeof(IApplicationHost).Assembly;
 893
 894            // Include composable parts in the Controller assembly
 28895            yield return typeof(IServerApplicationHost).Assembly;
 896
 897            // Include composable parts in the Providers assembly
 28898            yield return typeof(ProviderManager).Assembly;
 899
 900            // Include composable parts in the Photos assembly
 28901            yield return typeof(PhotoProvider).Assembly;
 902
 903            // Emby.Server implementations
 28904            yield return typeof(InstallationManager).Assembly;
 905
 906            // MediaEncoding
 28907            yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly;
 908
 909            // Local metadata
 28910            yield return typeof(BoxSetXmlSaver).Assembly;
 911
 912            // Xbmc
 28913            yield return typeof(ArtistNfoProvider).Assembly;
 914
 915            // Network
 28916            yield return typeof(NetworkManager).Assembly;
 917
 918            // Hls
 28919            yield return typeof(DynamicHlsPlaylistGenerator).Assembly;
 920
 336921            foreach (var i in GetAssembliesWithPartsInternal())
 922            {
 140923                yield return i;
 924            }
 28925        }
 926
 927        protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal();
 928
 929        /// <inheritdoc/>
 930        public string GetSmartApiUrl(IPAddress remoteAddr)
 931        {
 932            // Published server ends with a /
 0933            if (!string.IsNullOrEmpty(PublishedServerUrl))
 934            {
 935                // Published server ends with a '/', so we need to remove it.
 0936                return PublishedServerUrl.Trim('/');
 937            }
 938
 0939            string smart = NetManager.GetBindAddress(remoteAddr, out var port);
 0940            return GetLocalApiUrl(smart.Trim('/'), null, port);
 941        }
 942
 943        /// <inheritdoc/>
 944        public string GetSmartApiUrl(HttpRequest request)
 945        {
 946            // Return the host in the HTTP request as the API URL if not configured otherwise
 0947            if (ConfigurationManager.GetNetworkConfiguration().EnablePublishedServerUriByRequest)
 948            {
 0949                int? requestPort = request.Host.Port;
 0950                if (requestPort is null
 0951                    || (requestPort == 80 && string.Equals(request.Scheme, "http", StringComparison.OrdinalIgnoreCase))
 0952                    || (requestPort == 443 && string.Equals(request.Scheme, "https", StringComparison.OrdinalIgnoreCase)
 953                {
 0954                    requestPort = -1;
 955                }
 956
 0957                return GetLocalApiUrl(request.Host.Host, request.Scheme, requestPort);
 958            }
 959
 0960            return GetSmartApiUrl(request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback);
 961        }
 962
 963        /// <inheritdoc/>
 964        public string GetSmartApiUrl(string hostname)
 965        {
 966            // Published server ends with a /
 0967            if (!string.IsNullOrEmpty(PublishedServerUrl))
 968            {
 969                // Published server ends with a '/', so we need to remove it.
 0970                return PublishedServerUrl.Trim('/');
 971            }
 972
 0973            string smart = NetManager.GetBindAddress(hostname, out var port);
 0974            return GetLocalApiUrl(smart.Trim('/'), null, port);
 975        }
 976
 977        /// <inheritdoc/>
 978        public string GetApiUrlForLocalAccess(IPAddress ipAddress = null, bool allowHttps = true)
 979        {
 980            // With an empty source, the port will be null
 0981            var smart = NetManager.GetBindAddress(ipAddress, out _, false);
 0982            var scheme = !allowHttps ? Uri.UriSchemeHttp : null;
 0983            int? port = !allowHttps ? HttpPort : null;
 0984            return GetLocalApiUrl(smart, scheme, port);
 985        }
 986
 987        /// <inheritdoc/>
 988        public string GetLocalApiUrl(string hostname, string scheme = null, int? port = null)
 989        {
 990            // If the smartAPI isn't already a complete URL then treat it as a host or ip.
 0991            if (hostname.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
 0992                || hostname.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
 993            {
 0994                return hostname.TrimEnd('/');
 995            }
 996
 997            // NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does
 998            // not. For consistency, always trim the trailing slash.
 0999            scheme ??= ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
 01000            var isHttps = string.Equals(scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase);
 01001            return new UriBuilder
 01002            {
 01003                Scheme = scheme,
 01004                Host = hostname,
 01005                Port = port ?? (isHttps ? HttpsPort : HttpPort),
 01006                Path = ConfigurationManager.GetNetworkConfiguration().BaseUrl
 01007            }.ToString().TrimEnd('/');
 1008        }
 1009
 1010        public IEnumerable<Assembly> GetApiPluginAssemblies()
 1011        {
 281012            var assemblies = _allConcreteTypes
 281013                .Where(i => typeof(ControllerBase).IsAssignableFrom(i))
 281014                .Select(i => i.Assembly)
 281015                .Distinct();
 1016
 1681017            foreach (var assembly in assemblies)
 1018            {
 561019                Logger.LogDebug("Found API endpoints in plugin {Name}", assembly.FullName);
 561020                yield return assembly;
 1021            }
 281022        }
 1023
 1024        /// <inheritdoc />
 1025        public void Dispose()
 1026        {
 281027            Dispose(true);
 281028            GC.SuppressFinalize(this);
 281029        }
 1030
 1031        /// <summary>
 1032        /// Releases unmanaged and - optionally - managed resources.
 1033        /// </summary>
 1034        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release o
 1035        protected virtual void Dispose(bool dispose)
 1036        {
 281037            if (_disposed)
 1038            {
 01039                return;
 1040            }
 1041
 281042            if (dispose)
 1043            {
 281044                var type = GetType();
 1045
 281046                Logger.LogInformation("Disposing {Type}", type.Name);
 1047
 1101048                foreach (var part in _disposableParts.ToArray())
 1049                {
 271050                    var partType = part.GetType();
 271051                    if (partType == type)
 1052                    {
 1053                        continue;
 1054                    }
 1055
 271056                    Logger.LogInformation("Disposing {Type}", partType.Name);
 1057
 1058                    try
 1059                    {
 271060                        part.Dispose();
 271061                    }
 01062                    catch (Exception ex)
 1063                    {
 01064                        Logger.LogError(ex, "Error disposing {Type}", partType.Name);
 01065                    }
 1066                }
 1067
 281068                _disposableParts.Clear();
 1069
 281070                _pluginManager?.Dispose();
 1071            }
 1072
 281073            _disposed = true;
 281074        }
 1075    }
 1076}