using MediaBrowser.Common.Logging; 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.Diagnostics; 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() { /// /// 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; } /// /// 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; } 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) { // Performs initializations that only occur once InitializeInternal(progress); // Performs initializations that can be reloaded at anytime await Reload(progress).ConfigureAwait(false); progress.Report(new TaskProgress { Description = "Loading Complete", PercentComplete = 100 }); } /// /// Performs initializations that only occur once /// protected virtual void InitializeInternal(IProgress progress) { ApplicationPaths = new TApplicationPathsType(); ReloadLogger(); progress.Report(new TaskProgress { Description = "Loading configuration", PercentComplete = 0 }); ReloadConfiguration(); progress.Report(new TaskProgress { Description = "Starting Http server", PercentComplete = 5 }); ReloadHttpServer(); } /// /// Performs initializations that can be reloaded at anytime /// public virtual async Task Reload(IProgress progress) { await Task.Run(() => { progress.Report(new TaskProgress { Description = "Loading Plugins", PercentComplete = 10 }); ReloadComposableParts(); }).ConfigureAwait(false); } /// /// Disposes the current logger and creates a new one /// private void ReloadLogger() { DisposeLogger(); DateTime now = DateTime.Now; string logFilePath = Path.Combine(ApplicationPaths.LogDirectoryPath, "log-" + now.ToString("dMyyyy") + "-" + now.Ticks + ".log"); Trace.Listeners.Add(new TextWriterTraceListener(logFilePath)); Trace.AutoFlush = true; Logger.LoggerInstance = new TraceLogger(); } /// /// Uses MEF to locate plugins /// Subclasses can use this to locate types within plugins /// private void ReloadComposableParts() { DisposeComposableParts(); var container = GetCompositionContainer(includeCurrentAssembly: true); container.ComposeParts(this); OnComposablePartsLoaded(); container.Catalog.Dispose(); container.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 catalog = new AggregateCatalog(pluginAssemblies.Select(a => new AssemblyCatalog(a))); // Include composable parts in the Common assembly // Uncomment this if it's ever needed //catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly())); if (includeCurrentAssembly) { // Include composable parts in the subclass assembly catalog.Catalogs.Add(new AssemblyCatalog(GetType().Assembly)); } return new CompositionContainer(catalog); } /// /// Fires after MEF finishes finding composable parts within plugin assemblies /// protected virtual void OnComposablePartsLoaded() { // Start-up each plugin foreach (BasePlugin 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 if (!File.Exists(ApplicationPaths.SystemConfigurationFilePath)) { Configuration = new TConfigurationType(); XmlSerializer.SerializeToFile(Configuration, ApplicationPaths.SystemConfigurationFilePath); } else { Configuration = XmlSerializer.DeserializeFromFile(ApplicationPaths.SystemConfigurationFilePath); } Logger.LoggerInstance.LogSeverity = Configuration.EnableDebugLevelLogging ? LogSeverity.Debug : LogSeverity.Info; } /// /// 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"); DisposeComposableParts(); DisposeHttpServer(); DisposeLogger(); } /// /// Disposes all objects gathered through MEF composable parts /// protected virtual void DisposeComposableParts() { DisposePlugins(); } /// /// Disposes all plugins /// private void DisposePlugins() { if (Plugins != null) { Logger.LogInfo("Disposing Plugins"); foreach (BasePlugin plugin in Plugins) { plugin.Dispose(); } } } /// /// Disposes the current HttpServer /// private void DisposeHttpServer() { if (HttpServer != null) { Logger.LogInfo("Disposing Http Server"); HttpServer.Dispose(); } if (HttpListener != null) { HttpListener.Dispose(); } } /// /// Disposes the current Logger instance /// private void DisposeLogger() { Trace.Listeners.Clear(); if (Logger.LoggerInstance != null) { Logger.LogInfo("Disposing Logger"); Logger.LoggerInstance.Dispose(); } } /// /// Gets the current application version /// public Version ApplicationVersion { get { return GetType().Assembly.GetName().Version; } } BaseApplicationPaths IKernel.ApplicationPaths { get { return ApplicationPaths; } } } public interface IKernel { BaseApplicationPaths ApplicationPaths { get; } KernelContext KernelContext { get; } Task Init(IProgress progress); Task Reload(IProgress progress); void Dispose(); } }