using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; using MediaBrowser.Common.Implementations.Logging; using MediaBrowser.Common.Implementations.NetworkManagement; 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.ScheduledTasks; using MediaBrowser.Common.Security; using MediaBrowser.Common.Updates; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Updates; using SimpleInjector; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Common.Implementations { /// /// Class BaseApplicationHost /// /// The type of the T application paths type. public abstract class BaseApplicationHost : IApplicationHost where TApplicationPathsType : class, IApplicationPaths, new() { /// /// 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 = new TApplicationPathsType(); /// /// The container /// protected readonly Container Container = new Container(); /// /// The json serializer /// public readonly IJsonSerializer JsonSerializer = new JsonSerializer(); /// /// The _XML serializer /// protected readonly IXmlSerializer XmlSerializer = new XmlSerializer(); /// /// Gets assemblies that failed to load /// /// The failed assemblies. public List FailedAssemblies { get; protected set; } /// /// Gets all types within all running assemblies /// /// All types. public Type[] AllTypes { 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. protected 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; set; } /// /// Initializes a new instance of the class. /// protected BaseApplicationHost() { FailedAssemblies = new List(); LogManager = new NlogManager(ApplicationPaths.LogDirectoryPath, LogFilePrefixName); ConfigurationManager = GetConfigurationManager(); } /// /// Inits this instance. /// /// Task. public virtual async Task Init() { IsFirstRun = !ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted; Logger = LogManager.GetLogger("App"); LogManager.ReloadLogger(ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging ? LogSeverity.Debug : LogSeverity.Info); OnLoggerLoaded(); DiscoverTypes(); Logger.Info("Version {0} initializing", ApplicationVersion); await RegisterResources().ConfigureAwait(false); FindParts(); } protected virtual void OnLoggerLoaded() { } /// /// Runs the startup tasks. /// /// Task. public virtual Task RunStartupTasks() { return Task.Run(() => { Resolve().AddTasks(GetExports(false)); Task.Run(() => ConfigureAutoRunAtStartup()); ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; }); } /// /// Gets the composable part assemblies. /// /// IEnumerable{Assembly}. protected abstract IEnumerable GetComposablePartAssemblies(); /// /// Gets the name of the log file prefix. /// /// The name of the log file prefix. protected abstract string LogFilePrefixName { get; } /// /// Gets the configuration manager. /// /// IConfigurationManager. protected abstract IConfigurationManager GetConfigurationManager(); /// /// Finds the parts. /// protected virtual void FindParts() { Plugins = GetExports(); } /// /// Discovers the types. /// protected void DiscoverTypes() { FailedAssemblies.Clear(); var assemblies = GetComposablePartAssemblies().ToArray(); foreach (var assembly in assemblies) { Logger.Info("Loading {0}", assembly.FullName); } AllTypes = assemblies.SelectMany(GetTypes).ToArray(); AllConcreteTypes = AllTypes.Where(t => t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType).ToArray(); } /// /// Registers resources that classes will depend on /// /// Task. protected virtual Task RegisterResources() { return Task.Run(() => { RegisterSingleInstance(ConfigurationManager); RegisterSingleInstance(this); RegisterSingleInstance(ApplicationPaths); TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, Logger); RegisterSingleInstance(JsonSerializer); RegisterSingleInstance(XmlSerializer); RegisterSingleInstance(LogManager); RegisterSingleInstance(Logger); RegisterSingleInstance(TaskManager); HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger); RegisterSingleInstance(HttpClient); NetworkManager = new NetworkManager(); RegisterSingleInstance(NetworkManager); SecurityManager = new PluginSecurityManager(this, HttpClient, JsonSerializer, ApplicationPaths); RegisterSingleInstance(SecurityManager); InstallationManager = new InstallationManager(Logger, this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, NetworkManager); RegisterSingleInstance(InstallationManager); }); } /// /// 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 it fails we can still get a list of the Types it was able to resolve return ex.Types.Where(t => t != null); } } /// /// 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.Error("Error creating {0}", ex, type.Name); throw; } } /// /// Registers the specified obj. /// /// /// The obj. /// if set to true [manage lifetime]. protected void RegisterSingleInstance(T obj, bool manageLifetime = true) where T : class { Container.RegisterSingle(obj); if (manageLifetime) { var disposable = obj as IDisposable; if (disposable != null) { DisposableParts.Add(disposable); } } } /// /// Registers the single instance. /// /// /// The func. protected void RegisterSingleInstance(Func func) where T : class { Container.RegisterSingle(func); } /// /// 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(CreateInstance).Cast().ToArray(); if (manageLiftime) { lock (DisposableParts) { DisposableParts.AddRange(parts.OfType()); } } return parts; } /// /// Gets the current application version /// /// The application version. public Version ApplicationVersion { get { return GetType().Assembly.GetName().Version; } } /// /// Defines the full path to our shortcut in the start menu /// protected abstract string ProductShortcutPath { 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) { ConfigureAutoRunAtStartup(); } /// /// Configures the auto run at startup. /// private void ConfigureAutoRunAtStartup() { if (ConfigurationManager.CommonConfiguration.RunAtStartup) { //Copy our shortut into the startup folder for this user File.Copy(ProductShortcutPath, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup),Path.GetFileName(ProductShortcutPath) ?? "MBstartup.lnk"), true); } else { //Remove our shortcut from the startup folder for this user try { File.Delete(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), Path.GetFileName(ProductShortcutPath) ?? "MBstartup.lnk")); } catch (FileNotFoundException) { //This is okay - trying to remove it anyway } } } /// /// Removes the plugin. /// /// The plugin. public void RemovePlugin(IPlugin plugin) { var list = Plugins.ToList(); list.Remove(plugin); Plugins = list; } /// /// Notifies that the kernel that a change has been made that requires a restart /// public void NotifyPendingRestart() { HasPendingRestart = true; 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); part.Dispose(); } } } /// /// Restarts this instance. /// public abstract void 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; } private Tuple _lastUpdateCheckResult; /// /// Checks for update. /// /// The cancellation token. /// The progress. /// Task{CheckForUpdateResult}. public async Task CheckForApplicationUpdate(CancellationToken cancellationToken, IProgress progress) { if (_lastUpdateCheckResult != null) { // Let dev users get results more often for testing purposes var cacheLength = ConfigurationManager.CommonConfiguration.SystemUpdateLevel == PackageVersionClass.Dev ? TimeSpan.FromHours(1) : TimeSpan.FromHours(12); if ((DateTime.UtcNow - _lastUpdateCheckResult.Item2) < cacheLength) { return _lastUpdateCheckResult.Item1; } } var result = await CheckForApplicationUpdateInternal(cancellationToken, progress).ConfigureAwait(false); _lastUpdateCheckResult = new Tuple(result, DateTime.UtcNow); return _lastUpdateCheckResult.Item1; } /// /// Checks for application update internal. /// /// The cancellation token. /// The progress. /// Task{CheckForUpdateResult}. private async Task CheckForApplicationUpdateInternal(CancellationToken cancellationToken, IProgress progress) { var availablePackages = await InstallationManager.GetAvailablePackagesWithoutRegistrationInfo(CancellationToken.None).ConfigureAwait(false); var version = InstallationManager.GetLatestCompatibleVersion(availablePackages, ApplicationUpdatePackageName, ConfigurationManager.CommonConfiguration.SystemUpdateLevel); return version != null ? new CheckForUpdateResult { AvailableVersion = version.version, IsUpdateAvailable = version.version > ApplicationVersion, Package = version } : new CheckForUpdateResult { AvailableVersion = ApplicationVersion, IsUpdateAvailable = false }; } /// /// Gets the name of the application update package. /// /// The name of the application update package. protected abstract string ApplicationUpdatePackageName { get; } /// /// Updates the application. /// /// The package that contains the update /// The cancellation token. /// The progress. /// Task. public async Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress progress) { await InstallationManager.InstallPackage(package, progress, cancellationToken).ConfigureAwait(false); EventHelper.QueueEventIfNotNull(ApplicationUpdated, this, new GenericEventArgs { Argument = package.version }, Logger); NotifyPendingRestart(); } /// /// Shuts down. /// public abstract void Shutdown(); } }