using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; using MediaBrowser.Common.Implementations.Archiving; using MediaBrowser.Common.Implementations.Devices; using MediaBrowser.Common.Implementations.IO; using MediaBrowser.Common.Implementations.ScheduledTasks; using MediaBrowser.Common.Implementations.Security; using MediaBrowser.Common.Implementations.Serialization; using MediaBrowser.Common.Implementations.Updates; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Progress; using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Common.Security; using MediaBrowser.Common.Updates; using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Updates; using ServiceStack; using SimpleInjector; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using CommonIO; namespace MediaBrowser.Common.Implementations { /// /// Class BaseApplicationHost /// /// The type of the T application paths type. public abstract class BaseApplicationHost : IApplicationHost, IDependencyContainer where TApplicationPathsType : class, IApplicationPaths { /// /// Occurs when [has pending restart changed]. /// public event EventHandler HasPendingRestartChanged; /// /// Occurs when [application updated]. /// public event EventHandler> ApplicationUpdated; /// /// Gets or sets 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; } /// /// Gets or sets the logger. /// /// The logger. protected ILogger Logger { get; private set; } /// /// Gets or sets the plugins. /// /// The plugins. public IEnumerable Plugins { get; protected set; } /// /// Gets or sets the log manager. /// /// The log manager. public ILogManager LogManager { get; protected set; } /// /// Gets the application paths. /// /// The application paths. protected TApplicationPathsType ApplicationPaths { get; private set; } /// /// The container /// protected readonly Container Container = new Container(); /// /// The json serializer /// public IJsonSerializer JsonSerializer { get; private set; } /// /// The _XML serializer /// protected readonly IXmlSerializer XmlSerializer; /// /// Gets assemblies that failed to load /// /// The failed assemblies. public List FailedAssemblies { get; protected set; } /// /// Gets all concrete types. /// /// All concrete types. public Type[] AllConcreteTypes { get; protected set; } /// /// The disposable parts /// protected readonly List DisposableParts = new List(); /// /// Gets a value indicating whether this instance is first run. /// /// true if this instance is first run; otherwise, false. public bool IsFirstRun { get; private set; } /// /// Gets the kernel. /// /// The kernel. protected ITaskManager TaskManager { get; private set; } /// /// Gets the security manager. /// /// The security manager. protected ISecurityManager SecurityManager { get; private set; } /// /// Gets the HTTP client. /// /// The HTTP client. public IHttpClient HttpClient { get; private set; } /// /// Gets the network manager. /// /// The network manager. protected INetworkManager NetworkManager { get; private set; } /// /// Gets the configuration manager. /// /// The configuration manager. protected IConfigurationManager ConfigurationManager { get; private set; } /// /// Gets or sets the installation manager. /// /// The installation manager. protected IInstallationManager InstallationManager { get; private set; } protected IFileSystem FileSystemManager { get; private set; } /// /// Gets or sets the zip client. /// /// The zip client. protected IZipClient ZipClient { get; private set; } protected IIsoManager IsoManager { get; private set; } /// /// Gets the name. /// /// The name. public abstract string Name { get; } /// /// Gets a value indicating whether this instance is running as service. /// /// true if this instance is running as service; otherwise, false. public abstract bool IsRunningAsService { get; } private DeviceId _deviceId; public string SystemId { get { if (_deviceId == null) { _deviceId = new DeviceId(ApplicationPaths, LogManager.GetLogger("SystemId"), FileSystemManager); } return _deviceId.Value; } } public virtual string OperatingSystemDisplayName { get { return Environment.OSVersion.VersionString; } } /// /// Initializes a new instance of the class. /// protected BaseApplicationHost(TApplicationPathsType applicationPaths, ILogManager logManager, IFileSystem fileSystem) { XmlSerializer = new XmlSerializer (fileSystem, logManager.GetLogger("XmlSerializer")); FailedAssemblies = new List(); ApplicationPaths = applicationPaths; LogManager = logManager; FileSystemManager = fileSystem; ConfigurationManager = GetConfigurationManager(); // Initialize this early in case the -v command line option is used Logger = LogManager.GetLogger("App"); } /// /// Inits this instance. /// /// Task. public virtual async Task Init(IProgress progress) { try { // https://github.com/ServiceStack/ServiceStack/blob/master/tests/ServiceStack.WebHost.IntegrationTests/Web.config#L4 Licensing.RegisterLicense("1001-e1JlZjoxMDAxLE5hbWU6VGVzdCBCdXNpbmVzcyxUeXBlOkJ1c2luZXNzLEhhc2g6UHVNTVRPclhvT2ZIbjQ5MG5LZE1mUTd5RUMzQnBucTFEbTE3TDczVEF4QUNMT1FhNXJMOWkzVjFGL2ZkVTE3Q2pDNENqTkQyUktRWmhvUVBhYTBiekJGUUZ3ZE5aZHFDYm9hL3lydGlwUHI5K1JsaTBYbzNsUC85cjVJNHE5QVhldDN6QkE4aTlvdldrdTgyTk1relY2eis2dFFqTThYN2lmc0JveHgycFdjPSxFeHBpcnk6MjAxMy0wMS0wMX0="); } catch { // Failing under mono } progress.Report(1); JsonSerializer = CreateJsonSerializer(); OnLoggerLoaded(true); LogManager.LoggerLoaded += (s, e) => OnLoggerLoaded(false); IsFirstRun = !ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted; progress.Report(2); LogManager.LogSeverity = ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging ? LogSeverity.Debug : LogSeverity.Info; progress.Report(3); DiscoverTypes(); progress.Report(14); SetHttpLimit(); progress.Report(15); var innerProgress = new ActionableProgress(); innerProgress.RegisterAction(p => progress.Report(.8 * p + 15)); await RegisterResources(innerProgress).ConfigureAwait(false); FindParts(); progress.Report(95); await InstallIsoMounters(CancellationToken.None).ConfigureAwait(false); progress.Report(100); } protected virtual void OnLoggerLoaded(bool isFirstLoad) { Logger.Info("Application version: {0}", ApplicationVersion); if (!isFirstLoad) { LogEnvironmentInfo(Logger, ApplicationPaths, false); } // Put the app config in the log for troubleshooting purposes Logger.LogMultiline("Application configuration:", LogSeverity.Info, new StringBuilder(JsonSerializer.SerializeToString(ConfigurationManager.CommonConfiguration))); if (Plugins != null) { var pluginBuilder = new StringBuilder(); foreach (var plugin in Plugins) { pluginBuilder.AppendLine(string.Format("{0} {1}", plugin.Name, plugin.Version)); } Logger.LogMultiline("Plugins:", LogSeverity.Info, pluginBuilder); } } public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, bool isStartup) { logger.LogMultiline("Emby", LogSeverity.Info, GetBaseExceptionMessage(appPaths)); } protected static StringBuilder GetBaseExceptionMessage(IApplicationPaths appPaths) { var builder = new StringBuilder(); builder.AppendLine(string.Format("Command line: {0}", string.Join(" ", Environment.GetCommandLineArgs()))); builder.AppendLine(string.Format("Operating system: {0}", Environment.OSVersion)); builder.AppendLine(string.Format("Processor count: {0}", Environment.ProcessorCount)); builder.AppendLine(string.Format("64-Bit OS: {0}", Environment.Is64BitOperatingSystem)); builder.AppendLine(string.Format("64-Bit Process: {0}", Environment.Is64BitProcess)); builder.AppendLine(string.Format("Program data path: {0}", appPaths.ProgramDataPath)); Type type = Type.GetType("Mono.Runtime"); if (type != null) { MethodInfo displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); if (displayName != null) { builder.AppendLine("Mono: " + displayName.Invoke(null, null)); } } builder.AppendLine(string.Format("Application Path: {0}", appPaths.ApplicationPath)); return builder; } protected virtual IJsonSerializer CreateJsonSerializer() { return new JsonSerializer(FileSystemManager, LogManager.GetLogger("JsonSerializer")); } private void SetHttpLimit() { try { // Increase the max http request limit ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit); } catch (Exception ex) { Logger.ErrorException("Error setting http limit", ex); } } /// /// Installs the iso mounters. /// /// The cancellation token. /// Task. private async Task InstallIsoMounters(CancellationToken cancellationToken) { var list = new List(); foreach (var isoMounter in GetExports()) { try { if (isoMounter.RequiresInstallation && !isoMounter.IsInstalled) { Logger.Info("Installing {0}", isoMounter.Name); await isoMounter.Install(cancellationToken).ConfigureAwait(false); } list.Add(isoMounter); } catch (Exception ex) { Logger.ErrorException("{0} failed to load.", ex, isoMounter.Name); } } IsoManager.AddParts(list); } /// /// Runs the startup tasks. /// /// Task. public virtual Task RunStartupTasks() { Resolve().AddTasks(GetExports(false)); ConfigureAutorun (); ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; return Task.FromResult (true); } /// /// Configures the autorun. /// private void ConfigureAutorun() { try { ConfigureAutoRunAtStartup(ConfigurationManager.CommonConfiguration.RunAtStartup); } catch (Exception ex) { Logger.ErrorException("Error configuring autorun", ex); } } /// /// Gets the composable part assemblies. /// /// IEnumerable{Assembly}. protected abstract IEnumerable GetComposablePartAssemblies(); /// /// Gets the configuration manager. /// /// IConfigurationManager. protected abstract IConfigurationManager GetConfigurationManager(); /// /// Finds the parts. /// protected virtual void FindParts() { RegisterModules(); ConfigurationManager.AddParts(GetExports()); Plugins = GetExports(); } /// /// Discovers the types. /// protected void DiscoverTypes() { FailedAssemblies.Clear(); var assemblies = GetComposablePartAssemblies().ToList(); foreach (var assembly in assemblies) { Logger.Info("Loading {0}", assembly.FullName); } AllConcreteTypes = assemblies .SelectMany(GetTypes) .Where(t => t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType) .ToArray(); } /// /// Registers resources that classes will depend on /// /// Task. protected virtual Task RegisterResources(IProgress progress) { RegisterSingleInstance(ConfigurationManager); RegisterSingleInstance(this); RegisterSingleInstance(ApplicationPaths); TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LogManager.GetLogger("TaskManager"), FileSystemManager); RegisterSingleInstance(JsonSerializer); RegisterSingleInstance(XmlSerializer); RegisterSingleInstance(LogManager); RegisterSingleInstance(Logger); RegisterSingleInstance(TaskManager); RegisterSingleInstance(FileSystemManager); HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager); RegisterSingleInstance(HttpClient); NetworkManager = CreateNetworkManager(LogManager.GetLogger("NetworkManager")); RegisterSingleInstance(NetworkManager); SecurityManager = new PluginSecurityManager(this, HttpClient, JsonSerializer, ApplicationPaths, LogManager); RegisterSingleInstance(SecurityManager); InstallationManager = new InstallationManager(LogManager.GetLogger("InstallationManager"), this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ConfigurationManager, FileSystemManager); RegisterSingleInstance(InstallationManager); ZipClient = new ZipClient(FileSystemManager); RegisterSingleInstance(ZipClient); IsoManager = new IsoManager(); RegisterSingleInstance(IsoManager); return Task.FromResult (true); } private void RegisterModules() { var moduleTypes = GetExportTypes(); foreach (var type in moduleTypes) { try { var instance = Activator.CreateInstance(type) as IDependencyModule; if (instance != null) instance.BindDependencies(this); } catch (Exception ex) { Logger.ErrorException("Error setting up dependency bindings for " + type.Name, ex); } } } /// /// Gets a list of types within an assembly /// This will handle situations that would normally throw an exception - such as a type within the assembly that depends on some other non-existant reference /// /// The assembly. /// IEnumerable{Type}. /// assembly protected IEnumerable GetTypes(Assembly assembly) { if (assembly == null) { throw new ArgumentNullException("assembly"); } try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { if (ex.LoaderExceptions != null) { foreach (var loaderException in ex.LoaderExceptions) { Logger.Error("LoaderException: " + loaderException.Message); } } // If it fails we can still get a list of the Types it was able to resolve return ex.Types.Where(t => t != null); } } protected abstract INetworkManager CreateNetworkManager(ILogger logger); /// /// Creates an instance of type and resolves all constructor dependancies /// /// The type. /// System.Object. public object CreateInstance(Type type) { try { return Container.GetInstance(type); } catch (Exception ex) { Logger.ErrorException("Error creating {0}", ex, type.Name); throw; } } /// /// Creates the instance safe. /// /// The type. /// System.Object. protected object CreateInstanceSafe(Type type) { try { return Container.GetInstance(type); } catch (Exception ex) { Logger.ErrorException("Error creating {0}", ex, type.Name); // Don't blow up in release mode return null; } } void IDependencyContainer.RegisterSingleInstance(T obj, bool manageLifetime) { RegisterSingleInstance(obj, manageLifetime); } /// /// Registers the specified obj. /// /// /// The obj. /// if set to true [manage lifetime]. protected void RegisterSingleInstance(T obj, bool manageLifetime = true) where T : class { Container.RegisterSingleton(obj); if (manageLifetime) { var disposable = obj as IDisposable; if (disposable != null) { DisposableParts.Add(disposable); } } } void IDependencyContainer.RegisterSingleInstance(Func func) { RegisterSingleInstance(func); } /// /// Registers the single instance. /// /// /// The func. protected void RegisterSingleInstance(Func func) where T : class { Container.RegisterSingleton(func); } void IDependencyContainer.Register(Type typeInterface, Type typeImplementation) { Container.Register(typeInterface, typeImplementation); } /// /// Resolves this instance. /// /// /// ``0. public T Resolve() { return (T)Container.GetRegistration(typeof(T), true).GetInstance(); } /// /// Resolves this instance. /// /// /// ``0. public T TryResolve() { var result = Container.GetRegistration(typeof(T), false); if (result == null) { return default(T); } return (T)result.GetInstance(); } /// /// Loads the assembly. /// /// The file. /// Assembly. protected Assembly LoadAssembly(string file) { try { return Assembly.Load(File.ReadAllBytes(file)); } catch (Exception ex) { FailedAssemblies.Add(file); Logger.ErrorException("Error loading assembly {0}", ex, file); return null; } } /// /// Gets the export types. /// /// /// IEnumerable{Type}. public IEnumerable GetExportTypes() { var currentType = typeof(T); return AllConcreteTypes.AsParallel().Where(currentType.IsAssignableFrom); } /// /// Gets the exports. /// /// /// if set to true [manage liftime]. /// IEnumerable{``0}. public IEnumerable GetExports(bool manageLiftime = true) { var parts = GetExportTypes() .Select(CreateInstanceSafe) .Where(i => i != null) .Cast() .ToList(); if (manageLiftime) { lock (DisposableParts) { DisposableParts.AddRange(parts.OfType()); } } return parts; } /// /// Gets the application version. /// /// The application version. public abstract Version ApplicationVersion { get; } /// /// Handles the ConfigurationUpdated event of the ConfigurationManager control. /// /// The source of the event. /// The instance containing the event data. /// protected virtual void OnConfigurationUpdated(object sender, EventArgs e) { ConfigureAutorun(); } protected abstract void ConfigureAutoRunAtStartup(bool autorun); /// /// Removes the plugin. /// /// The plugin. public void RemovePlugin(IPlugin plugin) { var list = Plugins.ToList(); list.Remove(plugin); Plugins = list; } /// /// Gets a value indicating whether this instance can self restart. /// /// true if this instance can self restart; otherwise, false. public abstract bool CanSelfRestart { get; } /// /// Notifies that the kernel that a change has been made that requires a restart /// public void NotifyPendingRestart() { var changed = !HasPendingRestart; HasPendingRestart = true; if (changed) { EventHelper.QueueEventIfNotNull(HasPendingRestartChanged, this, EventArgs.Empty, Logger); } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); } /// /// 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 (dispose) { var type = GetType(); Logger.Info("Disposing " + type.Name); var parts = DisposableParts.Distinct().Where(i => i.GetType() != type).ToList(); DisposableParts.Clear(); foreach (var part in parts) { Logger.Info("Disposing " + part.GetType().Name); try { part.Dispose(); } catch (Exception ex) { Logger.ErrorException("Error disposing {0}", ex, part.GetType().Name); } } } } /// /// Restarts this instance. /// public abstract Task Restart(); /// /// Gets or sets a value indicating whether this instance can self update. /// /// true if this instance can self update; otherwise, false. public abstract bool CanSelfUpdate { get; } /// /// Checks for update. /// /// The cancellation token. /// The progress. /// Task{CheckForUpdateResult}. public abstract Task CheckForApplicationUpdate(CancellationToken cancellationToken, IProgress progress); /// /// Updates the application. /// /// The package that contains the update /// The cancellation token. /// The progress. /// Task. public abstract Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress progress); /// /// Shuts down. /// public abstract Task Shutdown(); /// /// Called when [application updated]. /// /// The package. protected void OnApplicationUpdated(PackageVersionInfo package) { Logger.Info("Application has been updated to version {0}", package.versionStr); EventHelper.FireEventIfNotNull(ApplicationUpdated, this, new GenericEventArgs { Argument = package }, Logger); NotifyPendingRestart(); } } }