using MediaBrowser.Common.Events; using MediaBrowser.Common.Logging; using MediaBrowser.Common.Mef; using MediaBrowser.Common.Net; using MediaBrowser.Common.Net.Handlers; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Serialization; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Progress; using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.ComponentModel.Composition.Primitives; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; namespace MediaBrowser.Common.Kernel { /// /// Represents a shared base kernel for both the Ui and server apps /// public abstract class BaseKernel : IDisposable, IKernel where TConfigurationType : BaseApplicationConfiguration, new() where TApplicationPathsType : BaseApplicationPaths, new() { #region ReloadBeginning Event /// /// Fires whenever the kernel begins reloading /// public event EventHandler>> ReloadBeginning; private void OnReloadBeginning(IProgress progress) { if (ReloadBeginning != null) { ReloadBeginning(this, new GenericEventArgs> { Argument = progress }); } } #endregion #region ReloadCompleted Event /// /// Fires whenever the kernel completes reloading /// public event EventHandler>> ReloadCompleted; private void OnReloadCompleted(IProgress progress) { if (ReloadCompleted != null) { ReloadCompleted(this, new GenericEventArgs> { Argument = progress }); } } #endregion /// /// Gets the current configuration /// public TConfigurationType Configuration { get; private set; } public TApplicationPathsType ApplicationPaths { get; private set; } /// /// Gets the list of currently loaded plugins /// [ImportMany(typeof(BasePlugin))] public IEnumerable Plugins { get; private set; } /// /// Gets the list of currently registered http handlers /// [ImportMany(typeof(BaseHandler))] private IEnumerable HttpHandlers { get; set; } /// /// Gets the list of currently registered Loggers /// [ImportMany(typeof(BaseLogger))] public IEnumerable Loggers { get; set; } /// /// Both the Ui and server will have a built-in HttpServer. /// People will inevitably want remote control apps so it's needed in the Ui too. /// public HttpServer HttpServer { get; private set; } /// /// This subscribes to HttpListener requests and finds the appropate BaseHandler to process it /// private IDisposable HttpListener { get; set; } /// /// Gets the MEF CompositionContainer /// private CompositionContainer CompositionContainer { get; set; } protected virtual string HttpServerUrlPrefix { get { return "http://+:" + Configuration.HttpServerPortNumber + "/mediabrowser/"; } } /// /// Gets the kernel context. Subclasses will have to override. /// public abstract KernelContext KernelContext { get; } /// /// Initializes the Kernel /// public async Task Init(IProgress progress) { Logger.Kernel = this; // Performs initializations that only occur once InitializeInternal(progress); // Performs initializations that can be reloaded at anytime await Reload(progress).ConfigureAwait(false); } /// /// Performs initializations that only occur once /// protected virtual void InitializeInternal(IProgress progress) { ApplicationPaths = new TApplicationPathsType(); ReportProgress(progress, "Loading Configuration"); ReloadConfiguration(); ReportProgress(progress, "Loading Http Server"); ReloadHttpServer(); } /// /// Performs initializations that can be reloaded at anytime /// public async Task Reload(IProgress progress) { OnReloadBeginning(progress); await ReloadInternal(progress).ConfigureAwait(false); OnReloadCompleted(progress); ReportProgress(progress, "Kernel.Reload Complete"); } /// /// Performs initializations that can be reloaded at anytime /// protected virtual async Task ReloadInternal(IProgress progress) { await Task.Run(() => { ReportProgress(progress, "Loading Plugins"); ReloadComposableParts(); }).ConfigureAwait(false); } /// /// Uses MEF to locate plugins /// Subclasses can use this to locate types within plugins /// private void ReloadComposableParts() { DisposeComposableParts(); CompositionContainer = GetCompositionContainer(includeCurrentAssembly: true); CompositionContainer.ComposeParts(this); OnComposablePartsLoaded(); CompositionContainer.Catalog.Dispose(); } /// /// Constructs an MEF CompositionContainer based on the current running assembly and all plugin assemblies /// public CompositionContainer GetCompositionContainer(bool includeCurrentAssembly = false) { // Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that // This will prevent the .dll file from getting locked, and allow us to replace it when needed IEnumerable pluginAssemblies = Directory.GetFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly).Select(f => Assembly.Load(File.ReadAllBytes((f)))); var catalogs = new List(); catalogs.AddRange(pluginAssemblies.Select(a => new AssemblyCatalog(a))); // Include composable parts in the Common assembly catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly())); if (includeCurrentAssembly) { // Include composable parts in the subclass assembly catalogs.Add(new AssemblyCatalog(GetType().Assembly)); } return MefUtils.GetSafeCompositionContainer(catalogs); } /// /// Fires after MEF finishes finding composable parts within plugin assemblies /// protected virtual void OnComposablePartsLoaded() { foreach (var logger in Loggers) { logger.Initialize(this); } // Start-up each plugin foreach (var plugin in Plugins) { plugin.Initialize(this); } } /// /// Reloads application configuration from the config file /// private void ReloadConfiguration() { //Configuration information for anything other than server-specific configuration will have to come via the API... -ebr // Deserialize config // Use try/catch to avoid the extra file system lookup using File.Exists try { Configuration = XmlSerializer.DeserializeFromFile(ApplicationPaths.SystemConfigurationFilePath); } catch (FileNotFoundException) { Configuration = new TConfigurationType(); XmlSerializer.SerializeToFile(Configuration, ApplicationPaths.SystemConfigurationFilePath); } } /// /// Restarts the Http Server, or starts it if not currently running /// private void ReloadHttpServer() { DisposeHttpServer(); HttpServer = new HttpServer(HttpServerUrlPrefix); HttpListener = HttpServer.Subscribe(ctx => { BaseHandler handler = HttpHandlers.FirstOrDefault(h => h.HandlesRequest(ctx.Request)); // Find the appropiate http handler if (handler != null) { // Need to create a new instance because handlers are currently stateful handler = Activator.CreateInstance(handler.GetType()) as BaseHandler; // No need to await this, despite the compiler warning handler.ProcessRequest(ctx); } }); } /// /// Disposes all resources currently in use. /// public virtual void Dispose() { Logger.LogInfo("Beginning Kernel.Dispose"); DisposeHttpServer(); DisposeComposableParts(); } /// /// Disposes all objects gathered through MEF composable parts /// protected virtual void DisposeComposableParts() { if (CompositionContainer != null) { CompositionContainer.Dispose(); } } /// /// Disposes the current HttpServer /// private void DisposeHttpServer() { if (HttpServer != null) { Logger.LogInfo("Disposing Http Server"); HttpServer.Dispose(); } if (HttpListener != null) { HttpListener.Dispose(); } } /// /// Gets the current application version /// public Version ApplicationVersion { get { return GetType().Assembly.GetName().Version; } } protected void ReportProgress(IProgress progress, string message) { progress.Report(new TaskProgress { Description = message }); Logger.LogInfo(message); } BaseApplicationPaths IKernel.ApplicationPaths { get { return ApplicationPaths; } } } public interface IKernel { BaseApplicationPaths ApplicationPaths { get; } KernelContext KernelContext { get; } Task Init(IProgress progress); Task Reload(IProgress progress); IEnumerable Loggers { get; } void Dispose(); } }