#pragma warning disable CS1591 using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Reflection; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Emby.Dlna; using Emby.Dlna.Main; using Emby.Dlna.Ssdp; using Emby.Drawing; using Emby.Notifications; using Emby.Photos; using Emby.Server.Implementations.Archiving; using Emby.Server.Implementations.Channels; using Emby.Server.Implementations.Collections; using Emby.Server.Implementations.Configuration; using Emby.Server.Implementations.Cryptography; using Emby.Server.Implementations.Data; using Emby.Server.Implementations.Devices; using Emby.Server.Implementations.Dto; using Emby.Server.Implementations.HttpServer.Security; using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Library; using Emby.Server.Implementations.LiveTv; using Emby.Server.Implementations.Localization; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Plugins; using Emby.Server.Implementations.QuickConnect; using Emby.Server.Implementations.ScheduledTasks; using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SyncPlay; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; using Jellyfin.Api.Helpers; using Jellyfin.Networking.Configuration; using Jellyfin.Networking.Manager; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; using MediaBrowser.Common.Json; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.SyncPlay; using MediaBrowser.Controller.TV; using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Plugins.Tmdb; using MediaBrowser.Providers.Subtitles; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Prometheus.DotNetRuntime; using JsonSerializer = System.Text.Json.JsonSerializer; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager; namespace Emby.Server.Implementations { /// /// Class CompositionRoot. /// public abstract class ApplicationHost : IServerApplicationHost, IDisposable { /// /// The environment variable prefixes to log at server startup. /// private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" }; private readonly IFileSystem _fileSystemManager; private readonly IXmlSerializer _xmlSerializer; private readonly IStartupOptions _startupOptions; private IMediaEncoder _mediaEncoder; private ISessionManager _sessionManager; private string[] _urlPrefixes; /// /// Gets a value indicating whether this instance can self restart. /// public bool CanSelfRestart => _startupOptions.RestartPath != null; public bool CoreStartupHasCompleted { get; private set; } public virtual bool CanLaunchWebBrowser { get { if (!Environment.UserInteractive) { return false; } if (_startupOptions.IsService) { return false; } if (OperatingSystem.Id == OperatingSystemId.Windows || OperatingSystem.Id == OperatingSystemId.Darwin) { return true; } return false; } } /// /// Gets the singleton instance. /// public INetworkManager NetManager { get; internal set; } /// /// Occurs when [has pending restart changed]. /// public event EventHandler HasPendingRestartChanged; /// /// Gets a value indicating whether this instance has changes that require the entire application to restart. /// /// true if this instance has pending application restart; otherwise, false. public bool HasPendingRestart { get; private set; } /// public bool IsShuttingDown { get; private set; } /// /// Gets the logger. /// protected ILogger Logger { get; } protected IServiceCollection ServiceCollection { get; } private IPlugin[] _plugins; private IReadOnlyList _pluginsManifests; /// /// Gets the plugins. /// /// The plugins. public IReadOnlyList Plugins => _plugins; /// /// Gets the logger factory. /// protected ILoggerFactory LoggerFactory { get; } /// /// Gets or sets the application paths. /// /// The application paths. protected IServerApplicationPaths ApplicationPaths { get; set; } /// /// Gets or sets all concrete types. /// /// All concrete types. private Type[] _allConcreteTypes; /// /// The disposable parts. /// private readonly List _disposableParts = new List(); /// /// Gets or sets the configuration manager. /// /// The configuration manager. protected IConfigurationManager ConfigurationManager { get; set; } /// /// Gets or sets the service provider. /// public IServiceProvider ServiceProvider { get; set; } /// /// Gets the http port for the webhost. /// public int HttpPort { get; private set; } /// /// Gets the https port for the webhost. /// public int HttpsPort { get; private set; } /// /// Gets the server configuration manager. /// /// The server configuration manager. public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. public ApplicationHost( IServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IStartupOptions options, IFileSystem fileSystem, IServiceCollection serviceCollection) { _xmlSerializer = new MyXmlSerializer(); ServiceCollection = serviceCollection; ApplicationPaths = applicationPaths; LoggerFactory = loggerFactory; _fileSystemManager = fileSystem; ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager); // Have to migrate settings here as migration subsystem not yet initialised. MigrateNetworkConfiguration(); // Have to pre-register the NetworkConfigurationFactory, as the configuration sub-system is not yet initialised. ConfigurationManager.RegisterConfiguration(); NetManager = new NetworkManager((IServerConfigurationManager)ConfigurationManager, LoggerFactory.CreateLogger()); Logger = LoggerFactory.CreateLogger(); _startupOptions = options; // Initialize runtime stat collection if (ServerConfigurationManager.Configuration.EnableMetrics) { DotNetRuntimeStatsBuilder.Default().StartCollecting(); } fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version; ApplicationVersionString = ApplicationVersion.ToString(3); ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString; } /// /// Temporary function to migration network settings out of system.xml and into network.xml. /// TODO: remove at the point when a fixed migration path has been decided upon. /// private void MigrateNetworkConfiguration() { string path = Path.Combine(ConfigurationManager.CommonApplicationPaths.ConfigurationDirectoryPath, "network.xml"); if (!File.Exists(path)) { var networkSettings = new NetworkConfiguration(); ClassMigrationHelper.CopyProperties(ServerConfigurationManager.Configuration, networkSettings); _xmlSerializer.SerializeToFile(networkSettings, path); Logger?.LogDebug("Successfully migrated network settings."); } } public string ExpandVirtualPath(string path) { var appPaths = ApplicationPaths; return path.Replace(appPaths.VirtualDataPath, appPaths.DataPath, StringComparison.OrdinalIgnoreCase) .Replace(appPaths.VirtualInternalMetadataPath, appPaths.InternalMetadataPath, StringComparison.OrdinalIgnoreCase); } public string ReverseVirtualPath(string path) { var appPaths = ApplicationPaths; return path.Replace(appPaths.DataPath, appPaths.VirtualDataPath, StringComparison.OrdinalIgnoreCase) .Replace(appPaths.InternalMetadataPath, appPaths.VirtualInternalMetadataPath, StringComparison.OrdinalIgnoreCase); } /// public Version ApplicationVersion { get; } /// public string ApplicationVersionString { get; } /// /// Gets the current application user agent. /// /// The application user agent. public string ApplicationUserAgent { get; } /// /// Gets the email address for use within a comment section of a user agent field. /// Presently used to provide contact information to MusicBrainz service. /// public string ApplicationUserAgentAddress => "team@jellyfin.org"; /// /// Gets the current application name. /// /// The application name. public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName; private DeviceId _deviceId; public string SystemId { get { if (_deviceId == null) { _deviceId = new DeviceId(ApplicationPaths, LoggerFactory); } return _deviceId.Value; } } /// public string Name => ApplicationProductName; /// /// Creates an instance of type and resolves all constructor dependencies. /// /// The type. /// System.Object. public object CreateInstance(Type type) => ActivatorUtilities.CreateInstance(ServiceProvider, type); /// /// Creates an instance of type and resolves all constructor dependencies. /// /// /// The type. /// T. public T CreateInstance() => ActivatorUtilities.CreateInstance(ServiceProvider); /// /// Creates the instance safe. /// /// The type. /// System.Object. protected object CreateInstanceSafe(Type type) { try { Logger.LogDebug("Creating instance of {Type}", type); return ActivatorUtilities.CreateInstance(ServiceProvider, type); } catch (Exception ex) { Logger.LogError(ex, "Error creating {Type}", type); return null; } } /// /// Resolves this instance. /// /// The type. /// ``0. public T Resolve() => ServiceProvider.GetService(); /// /// Gets the export types. /// /// The type. /// IEnumerable{Type}. public IEnumerable GetExportTypes() { var currentType = typeof(T); return _allConcreteTypes.Where(i => currentType.IsAssignableFrom(i)); } /// public IReadOnlyCollection GetExports(bool manageLifetime = true) { // Convert to list so this isn't executed for each iteration var parts = GetExportTypes() .Select(CreateInstanceSafe) .Where(i => i != null) .Cast() .ToList(); if (manageLifetime) { lock (_disposableParts) { _disposableParts.AddRange(parts.OfType()); } } return parts; } /// /// Runs the startup tasks. /// /// . public async Task RunStartupTasksAsync() { Logger.LogInformation("Running startup tasks"); Resolve().AddTasks(GetExports(false)); ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; ConfigurationManager.NamedConfigurationUpdated += OnConfigurationUpdated; _mediaEncoder.SetFFmpegPath(); Logger.LogInformation("ServerId: {0}", SystemId); var entryPoints = GetExports(); var stopWatch = new Stopwatch(); stopWatch.Start(); await Task.WhenAll(StartEntryPoints(entryPoints, true)).ConfigureAwait(false); Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); Logger.LogInformation("Core startup complete"); CoreStartupHasCompleted = true; stopWatch.Restart(); await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false); Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed); stopWatch.Stop(); } private IEnumerable StartEntryPoints(IEnumerable entryPoints, bool isBeforeStartup) { foreach (var entryPoint in entryPoints) { if (isBeforeStartup != (entryPoint is IRunBeforeStartup)) { continue; } Logger.LogDebug("Starting entry point {Type}", entryPoint.GetType()); yield return entryPoint.RunAsync(); } } /// public void Init() { var networkConfiguration = ServerConfigurationManager.GetNetworkConfiguration(); HttpPort = networkConfiguration.HttpServerPortNumber; HttpsPort = networkConfiguration.HttpsPortNumber; // Safeguard against invalid configuration if (HttpPort == HttpsPort) { HttpPort = NetworkConfiguration.DefaultHttpPort; HttpsPort = NetworkConfiguration.DefaultHttpsPort; } CertificateInfo = new CertificateInfo { Path = networkConfiguration.CertificatePath, Password = networkConfiguration.CertificatePassword }; Certificate = GetCertificate(CertificateInfo); DiscoverTypes(); RegisterServices(); RegisterPluginServices(); } /// /// Registers services/resources with the service collection that will be available via DI. /// protected virtual void RegisterServices() { ServiceCollection.AddSingleton(_startupOptions); ServiceCollection.AddMemoryCache(); ServiceCollection.AddSingleton(ConfigurationManager); ServiceCollection.AddSingleton(this); ServiceCollection.AddSingleton(ApplicationPaths); ServiceCollection.AddSingleton(_fileSystemManager); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(NetManager); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(_xmlSerializer); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(this); ServiceCollection.AddSingleton(ApplicationPaths); ServiceCollection.AddSingleton(ServerConfigurationManager); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); // TODO: Refactor to eliminate the circular dependency here so that Lazy isn't required ServiceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); // TODO: Refactor to eliminate the circular dependency here so that Lazy isn't required ServiceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); ServiceCollection.AddSingleton(); // TODO: Refactor to eliminate the circular dependencies here so that Lazy isn't required ServiceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); ServiceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); ServiceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); // TODO: Refactor to eliminate the circular dependency here so that Lazy isn't required ServiceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddScoped(); ServiceCollection.AddScoped(); ServiceCollection.AddScoped(); } /// /// Create services registered with the service container that need to be initialized at application startup. /// /// A task representing the service initialization operation. public async Task InitializeServices() { var localizationManager = (LocalizationManager)Resolve(); await localizationManager.LoadAll().ConfigureAwait(false); _mediaEncoder = Resolve(); _sessionManager = Resolve(); ((AuthenticationRepository)Resolve()).Initialize(); SetStaticProperties(); var userDataRepo = (SqliteUserDataRepository)Resolve(); ((SqliteItemRepository)Resolve()).Initialize(userDataRepo, Resolve()); FindParts(); } public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths) { // Distinct these to prevent users from reporting problems that aren't actually problems var commandLineArgs = Environment .GetCommandLineArgs() .Distinct(); // Get all relevant environment variables var allEnvVars = Environment.GetEnvironmentVariables(); var relevantEnvVars = new Dictionary(); foreach (var key in allEnvVars.Keys) { if (_relevantEnvVarPrefixes.Any(prefix => key.ToString().StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) { relevantEnvVars.Add(key, allEnvVars[key]); } } logger.LogInformation("Environment Variables: {EnvVars}", relevantEnvVars); logger.LogInformation("Arguments: {Args}", commandLineArgs); logger.LogInformation("Operating system: {OS}", OperatingSystem.Name); logger.LogInformation("Architecture: {Architecture}", RuntimeInformation.OSArchitecture); logger.LogInformation("64-Bit Process: {Is64Bit}", Environment.Is64BitProcess); logger.LogInformation("User Interactive: {IsUserInteractive}", Environment.UserInteractive); logger.LogInformation("Processor count: {ProcessorCount}", Environment.ProcessorCount); logger.LogInformation("Program data path: {ProgramDataPath}", appPaths.ProgramDataPath); logger.LogInformation("Web resources path: {WebPath}", appPaths.WebPath); logger.LogInformation("Application directory: {ApplicationPath}", appPaths.ProgramSystemPath); } private X509Certificate2 GetCertificate(CertificateInfo info) { var certificateLocation = info?.Path; if (string.IsNullOrWhiteSpace(certificateLocation)) { return null; } try { if (!File.Exists(certificateLocation)) { return null; } // Don't use an empty string password var password = string.IsNullOrWhiteSpace(info.Password) ? null : info.Password; var localCert = new X509Certificate2(certificateLocation, password, X509KeyStorageFlags.UserKeySet); // localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; if (!localCert.HasPrivateKey) { Logger.LogError("No private key included in SSL cert {CertificateLocation}.", certificateLocation); return null; } return localCert; } catch (Exception ex) { Logger.LogError(ex, "Error loading cert from {CertificateLocation}", certificateLocation); return null; } } /// /// Dirty hacks. /// private void SetStaticProperties() { // For now there's no real way to inject these properly BaseItem.Logger = Resolve>(); BaseItem.ConfigurationManager = ServerConfigurationManager; BaseItem.LibraryManager = Resolve(); BaseItem.ProviderManager = Resolve(); BaseItem.LocalizationManager = Resolve(); BaseItem.ItemRepository = Resolve(); BaseItem.FileSystem = _fileSystemManager; BaseItem.UserDataManager = Resolve(); BaseItem.ChannelManager = Resolve(); Video.LiveTvManager = Resolve(); Folder.UserViewManager = Resolve(); UserView.TVSeriesManager = Resolve(); UserView.CollectionManager = Resolve(); BaseItem.MediaSourceManager = Resolve(); CollectionFolder.XmlSerializer = _xmlSerializer; CollectionFolder.ApplicationHost = this; } /// /// Finds plugin components and register them with the appropriate services. /// private void FindParts() { if (!ServerConfigurationManager.Configuration.IsPortAuthorized) { ServerConfigurationManager.Configuration.IsPortAuthorized = true; ConfigurationManager.SaveConfiguration(); } ConfigurationManager.AddParts(GetExports()); _plugins = GetExports() .Where(i => i != null) .ToArray(); if (Plugins != null) { foreach (var plugin in Plugins) { if (_pluginsManifests != null && plugin is IPluginAssembly assemblyPlugin) { // Ensure the version number matches the Plugin Manifest information. foreach (var item in _pluginsManifests) { if (Path.GetDirectoryName(plugin.AssemblyFilePath).Equals(item.Path, StringComparison.OrdinalIgnoreCase)) { // Update version number to that of the manifest. assemblyPlugin.SetAttributes( plugin.AssemblyFilePath, Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(plugin.AssemblyFilePath)), item.Version); break; } } } Logger.LogInformation("Loaded plugin: {PluginName} {PluginVersion}", plugin.Name, plugin.Version); } } _urlPrefixes = GetUrlPrefixes().ToArray(); Resolve().AddParts( GetExports(), GetExports(), GetExports(), GetExports(), GetExports()); Resolve().AddParts( GetExports(), GetExports(), GetExports(), GetExports(), GetExports()); Resolve().AddParts(GetExports(), GetExports(), GetExports()); Resolve().AddParts(GetExports()); Resolve().AddParts(GetExports()); Resolve().AddParts(GetExports()); Resolve().AddParts(GetExports(), GetExports()); } /// /// Discovers the types. /// protected void DiscoverTypes() { Logger.LogInformation("Loading assemblies"); _allConcreteTypes = GetTypes(GetComposablePartAssemblies()).ToArray(); } private void RegisterPluginServices() { foreach (var pluginServiceRegistrator in GetExportTypes()) { try { var instance = (IPluginServiceRegistrator)Activator.CreateInstance(pluginServiceRegistrator); instance.RegisterServices(ServiceCollection); } catch (Exception ex) { Logger.LogError(ex, "Error registering plugin services from {Assembly}.", pluginServiceRegistrator.Assembly); } } } private IEnumerable GetTypes(IEnumerable assemblies) { foreach (var ass in assemblies) { Type[] exportedTypes; try { exportedTypes = ass.GetExportedTypes(); } catch (FileNotFoundException ex) { Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName); continue; } catch (TypeLoadException ex) { Logger.LogError(ex, "Error loading types from {Assembly}.", ass.FullName); continue; } foreach (Type type in exportedTypes) { if (type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType) { yield return type; } } } } private CertificateInfo CertificateInfo { get; set; } public X509Certificate2 Certificate { get; private set; } private IEnumerable GetUrlPrefixes() { var hosts = new[] { "+" }; return hosts.SelectMany(i => { var prefixes = new List { "http://" + i + ":" + HttpPort + "/" }; if (CertificateInfo != null) { prefixes.Add("https://" + i + ":" + HttpsPort + "/"); } return prefixes; }); } /// /// Called when [configuration updated]. /// /// The sender. /// The instance containing the event data. protected void OnConfigurationUpdated(object sender, EventArgs e) { var requiresRestart = false; var networkConfiguration = ServerConfigurationManager.GetNetworkConfiguration(); // Don't do anything if these haven't been set yet if (HttpPort != 0 && HttpsPort != 0) { // Need to restart if ports have changed if (networkConfiguration.HttpServerPortNumber != HttpPort || networkConfiguration.HttpsPortNumber != HttpsPort) { if (ServerConfigurationManager.Configuration.IsPortAuthorized) { ServerConfigurationManager.Configuration.IsPortAuthorized = false; ServerConfigurationManager.SaveConfiguration(); requiresRestart = true; } } } if (!_urlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase)) { requiresRestart = true; } if (ValidateSslCertificate(networkConfiguration)) { requiresRestart = true; } if (requiresRestart) { Logger.LogInformation("App needs to be restarted due to configuration change."); NotifyPendingRestart(); } } /// /// Validates the SSL certificate. /// /// The new configuration. /// The certificate path doesn't exist. private bool ValidateSslCertificate(NetworkConfiguration networkConfig) { var newPath = networkConfig.CertificatePath; if (!string.IsNullOrWhiteSpace(newPath) && !string.Equals(CertificateInfo?.Path, newPath, StringComparison.Ordinal)) { if (File.Exists(newPath)) { return true; } throw new FileNotFoundException( string.Format( CultureInfo.InvariantCulture, "Certificate file '{0}' does not exist.", newPath)); } return false; } /// /// Notifies that the kernel that a change has been made that requires a restart. /// public void NotifyPendingRestart() { Logger.LogInformation("App needs to be restarted."); var changed = !HasPendingRestart; HasPendingRestart = true; if (changed) { EventHelper.QueueEventIfNotNull(HasPendingRestartChanged, this, EventArgs.Empty, Logger); } } /// /// Restarts this instance. /// public void Restart() { if (!CanSelfRestart) { throw new PlatformNotSupportedException("The server is unable to self-restart. Please restart manually."); } if (IsShuttingDown) { return; } IsShuttingDown = true; Task.Run(async () => { try { await _sessionManager.SendServerRestartNotification(CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { Logger.LogError(ex, "Error sending server restart notification"); } Logger.LogInformation("Calling RestartInternal"); RestartInternal(); }); } protected abstract void RestartInternal(); /// public IEnumerable GetLocalPlugins(string path, bool cleanup = true) { var minimumVersion = new Version(0, 0, 0, 1); var versions = new List(); if (!Directory.Exists(path)) { // Plugin path doesn't exist, don't try to enumerate subfolders. return Enumerable.Empty(); } var directories = Directory.EnumerateDirectories(path, "*.*", SearchOption.TopDirectoryOnly); foreach (var dir in directories) { try { var metafile = Path.Combine(dir, "meta.json"); if (File.Exists(metafile)) { var jsonString = File.ReadAllText(metafile); var manifest = JsonSerializer.Deserialize(jsonString, JsonDefaults.GetOptions()); if (!Version.TryParse(manifest.TargetAbi, out var targetAbi)) { targetAbi = minimumVersion; } if (!Version.TryParse(manifest.Version, out var version)) { version = minimumVersion; } if (ApplicationVersion >= targetAbi) { // Only load Plugins if the plugin is built for this version or below. versions.Add(new LocalPlugin(manifest.Guid, manifest.Name, version, dir)); } } else { // No metafile, so lets see if the folder is versioned. metafile = dir.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries)[^1]; int versionIndex = dir.LastIndexOf('_'); if (versionIndex != -1 && Version.TryParse(dir.AsSpan()[(versionIndex + 1)..], out Version parsedVersion)) { // Versioned folder. versions.Add(new LocalPlugin(Guid.Empty, metafile, parsedVersion, dir)); } else { // Un-versioned folder - Add it under the path name and version 0.0.0.1. versions.Add(new LocalPlugin(Guid.Empty, metafile, minimumVersion, dir)); } } } catch { continue; } } string lastName = string.Empty; versions.Sort(LocalPlugin.Compare); // Traverse backwards through the list. // The first item will be the latest version. for (int x = versions.Count - 1; x >= 0; x--) { if (!string.Equals(lastName, versions[x].Name, StringComparison.OrdinalIgnoreCase)) { versions[x].DllFiles.AddRange(Directory.EnumerateFiles(versions[x].Path, "*.dll", SearchOption.AllDirectories)); lastName = versions[x].Name; continue; } if (!string.IsNullOrEmpty(lastName) && cleanup) { // Attempt a cleanup of old folders. try { Logger.LogDebug("Deleting {Path}", versions[x].Path); Directory.Delete(versions[x].Path, true); } catch (Exception e) { Logger.LogWarning(e, "Unable to delete {Path}", versions[x].Path); } versions.RemoveAt(x); } } return versions; } /// /// Gets the composable part assemblies. /// /// IEnumerable{Assembly}. protected IEnumerable GetComposablePartAssemblies() { if (Directory.Exists(ApplicationPaths.PluginsPath)) { _pluginsManifests = GetLocalPlugins(ApplicationPaths.PluginsPath).ToList(); foreach (var plugin in _pluginsManifests) { foreach (var file in plugin.DllFiles) { Assembly plugAss; try { plugAss = Assembly.LoadFrom(file); } catch (FileLoadException ex) { Logger.LogError(ex, "Failed to load assembly {Path}", file); continue; } Logger.LogInformation("Loaded assembly {Assembly} from {Path}", plugAss.FullName, file); yield return plugAss; } } } // Include composable parts in the Model assembly yield return typeof(SystemInfo).Assembly; // Include composable parts in the Common assembly yield return typeof(IApplicationHost).Assembly; // Include composable parts in the Controller assembly yield return typeof(IServerApplicationHost).Assembly; // Include composable parts in the Providers assembly yield return typeof(ProviderUtils).Assembly; // Include composable parts in the Photos assembly yield return typeof(PhotoProvider).Assembly; // Emby.Server implementations yield return typeof(InstallationManager).Assembly; // MediaEncoding yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly; // Dlna yield return typeof(DlnaEntryPoint).Assembly; // Local metadata yield return typeof(BoxSetXmlSaver).Assembly; // Notifications yield return typeof(NotificationManager).Assembly; // Xbmc yield return typeof(ArtistNfoProvider).Assembly; // Network yield return typeof(NetworkManager).Assembly; foreach (var i in GetAssembliesWithPartsInternal()) { yield return i; } } protected abstract IEnumerable GetAssembliesWithPartsInternal(); /// /// Gets the system status. /// /// Where this request originated. /// SystemInfo. public SystemInfo GetSystemInfo(IPAddress source) { return new SystemInfo { HasPendingRestart = HasPendingRestart, IsShuttingDown = IsShuttingDown, Version = ApplicationVersionString, WebSocketPortNumber = HttpPort, CompletedInstallations = Resolve().CompletedInstallations.ToArray(), Id = SystemId, ProgramDataPath = ApplicationPaths.ProgramDataPath, WebPath = ApplicationPaths.WebPath, LogPath = ApplicationPaths.LogDirectoryPath, ItemsByNamePath = ApplicationPaths.InternalMetadataPath, InternalMetadataPath = ApplicationPaths.InternalMetadataPath, CachePath = ApplicationPaths.CachePath, OperatingSystem = OperatingSystem.Id.ToString(), OperatingSystemDisplayName = OperatingSystem.Name, CanSelfRestart = CanSelfRestart, CanLaunchWebBrowser = CanLaunchWebBrowser, HasUpdateAvailable = HasUpdateAvailable, TranscodingTempPath = ConfigurationManager.GetTranscodePath(), ServerName = FriendlyName, LocalAddress = GetSmartApiUrl(source), SupportsLibraryMonitor = true, EncoderLocation = _mediaEncoder.EncoderLocation, SystemArchitecture = RuntimeInformation.OSArchitecture, PackageName = _startupOptions.PackageName }; } public IEnumerable GetWakeOnLanInfo() => NetManager.GetMacAddresses() .Select(i => new WakeOnLanInfo(i)) .ToList(); public PublicSystemInfo GetPublicSystemInfo(IPAddress source) { return new PublicSystemInfo { Version = ApplicationVersionString, ProductName = ApplicationProductName, Id = SystemId, OperatingSystem = OperatingSystem.Id.ToString(), ServerName = FriendlyName, LocalAddress = GetSmartApiUrl(source), StartupWizardCompleted = ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted }; } /// public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.GetNetworkConfiguration().EnableHttps; /// public string GetSmartApiUrl(IPAddress ipAddress, int? port = null) { // Published server ends with a / if (_startupOptions.PublishedServerUrl != null) { // Published server ends with a '/', so we need to remove it. return _startupOptions.PublishedServerUrl.ToString().Trim('/'); } string smart = NetManager.GetBindInterface(ipAddress, out port); // If the smartAPI doesn't start with http then treat it as a host or ip. if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { return smart.Trim('/'); } return GetLocalApiUrl(smart.Trim('/'), null, port); } /// public string GetSmartApiUrl(HttpRequest request, int? port = null) { // Published server ends with a / if (_startupOptions.PublishedServerUrl != null) { // Published server ends with a '/', so we need to remove it. return _startupOptions.PublishedServerUrl.ToString().Trim('/'); } string smart = NetManager.GetBindInterface(request, out port); // If the smartAPI doesn't start with http then treat it as a host or ip. if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { return smart.Trim('/'); } return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port); } /// public string GetSmartApiUrl(string hostname, int? port = null) { // Published server ends with a / if (_startupOptions.PublishedServerUrl != null) { // Published server ends with a '/', so we need to remove it. return _startupOptions.PublishedServerUrl.ToString().Trim('/'); } string smart = NetManager.GetBindInterface(hostname, out port); // If the smartAPI doesn't start with http then treat it as a host or ip. if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { return smart.Trim('/'); } return GetLocalApiUrl(smart.Trim('/'), null, port); } /// public string GetLoopbackHttpApiUrl() { if (NetManager.IsIP6Enabled) { return GetLocalApiUrl("::1", Uri.UriSchemeHttp, HttpPort); } return GetLocalApiUrl("127.0.0.1", Uri.UriSchemeHttp, HttpPort); } /// public string GetLocalApiUrl(string host, string scheme = null, int? port = null) { // NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does // not. For consistency, always trim the trailing slash. return new UriBuilder { Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp), Host = host, Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort), Path = ServerConfigurationManager.GetNetworkConfiguration().BaseUrl }.ToString().TrimEnd('/'); } public string FriendlyName => string.IsNullOrEmpty(ServerConfigurationManager.Configuration.ServerName) ? Environment.MachineName : ServerConfigurationManager.Configuration.ServerName; /// /// Shuts down. /// public async Task Shutdown() { if (IsShuttingDown) { return; } IsShuttingDown = true; try { await _sessionManager.SendServerShutdownNotification(CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { Logger.LogError(ex, "Error sending server shutdown notification"); } ShutdownInternal(); } protected abstract void ShutdownInternal(); public event EventHandler HasUpdateAvailableChanged; private bool _hasUpdateAvailable; public bool HasUpdateAvailable { get => _hasUpdateAvailable; set { var fireEvent = value && !_hasUpdateAvailable; _hasUpdateAvailable = value; if (fireEvent) { HasUpdateAvailableChanged?.Invoke(this, EventArgs.Empty); } } } /// /// Removes the plugin. /// /// The plugin. public void RemovePlugin(IPlugin plugin) { var list = _plugins.ToList(); list.Remove(plugin); _plugins = list.ToArray(); } public IEnumerable GetApiPluginAssemblies() { var assemblies = _allConcreteTypes .Where(i => typeof(ControllerBase).IsAssignableFrom(i)) .Select(i => i.Assembly) .Distinct(); foreach (var assembly in assemblies) { Logger.LogDebug("Found API endpoints in plugin {Name}", assembly.FullName); yield return assembly; } } public virtual void LaunchUrl(string url) { if (!CanLaunchWebBrowser) { throw new NotSupportedException(); } var process = new Process { StartInfo = new ProcessStartInfo { FileName = url, UseShellExecute = true, ErrorDialog = false }, EnableRaisingEvents = true }; process.Exited += (sender, args) => ((Process)sender).Dispose(); try { process.Start(); } catch (Exception ex) { Logger.LogError(ex, "Error launching url: {url}", url); throw; } } private bool _disposed = false; /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { if (_disposed) { return; } if (dispose) { var type = GetType(); Logger.LogInformation("Disposing {Type}", type.Name); var parts = _disposableParts.Distinct().Where(i => i.GetType() != type).ToList(); _disposableParts.Clear(); foreach (var part in parts) { Logger.LogInformation("Disposing {Type}", part.GetType().Name); try { part.Dispose(); } catch (Exception ex) { Logger.LogError(ex, "Error disposing {Type}", part.GetType().Name); } } } _disposed = true; } } internal class CertificateInfo { public string Path { get; set; } public string Password { get; set; } } }