using MediaBrowser.Common.Events; using MediaBrowser.Common.IO; using MediaBrowser.Common.Localization; using MediaBrowser.Common.Mef; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Common.Serialization; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Logging; using MediaBrowser.Model.System; using NLog; using NLog.Config; using NLog.Targets; using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.Deployment.Application; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Common.Kernel { /// /// Represents a shared base kernel for both the Ui and server apps /// /// The type of the T configuration type. /// The type of the T application paths type. public abstract class BaseKernel : IDisposable, IKernel where TConfigurationType : BaseApplicationConfiguration, new() where TApplicationPathsType : BaseApplicationPaths, new() { /// /// Occurs when [has pending restart changed]. /// public event EventHandler HasPendingRestartChanged; /// /// Notifiies the containing application that a restart has been requested /// public event EventHandler ApplicationRestartRequested; #region ConfigurationUpdated Event /// /// Occurs when [configuration updated]. /// public event EventHandler ConfigurationUpdated; /// /// Called when [configuration updated]. /// internal void OnConfigurationUpdated() { EventHelper.QueueEventIfNotNull(ConfigurationUpdated, this, EventArgs.Empty); // Notify connected clients TcpManager.SendWebSocketMessage("ConfigurationUpdated", Configuration); } #endregion #region LoggerLoaded Event /// /// Fires whenever the logger is loaded /// public event EventHandler LoggerLoaded; /// /// Called when [logger loaded]. /// private void OnLoggerLoaded() { EventHelper.QueueEventIfNotNull(LoggerLoaded, this, EventArgs.Empty); } #endregion #region ReloadBeginning Event /// /// Fires whenever the kernel begins reloading /// public event EventHandler ReloadBeginning; /// /// Called when [reload beginning]. /// private void OnReloadBeginning() { EventHelper.QueueEventIfNotNull(ReloadBeginning, this, EventArgs.Empty); } #endregion #region ReloadCompleted Event /// /// Fires whenever the kernel completes reloading /// public event EventHandler ReloadCompleted; /// /// Called when [reload completed]. /// private void OnReloadCompleted() { EventHelper.QueueEventIfNotNull(ReloadCompleted, this, EventArgs.Empty); } #endregion #region ApplicationUpdated Event /// /// Occurs when [application updated]. /// public event EventHandler> ApplicationUpdated; /// /// Called when [application updated]. /// /// The new version. public void OnApplicationUpdated(Version newVersion) { EventHelper.QueueEventIfNotNull(ApplicationUpdated, this, new GenericEventArgs {Argument = newVersion}); NotifyPendingRestart(); } #endregion /// /// The _configuration loaded /// private bool _configurationLoaded; /// /// The _configuration sync lock /// private object _configurationSyncLock = new object(); /// /// The _configuration /// private TConfigurationType _configuration; /// /// Gets the system configuration /// /// The configuration. public TConfigurationType Configuration { get { // Lazy load LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => XmlSerializer.GetXmlConfiguration(ApplicationPaths.SystemConfigurationFilePath)); return _configuration; } protected set { _configuration = value; if (value == null) { _configurationLoaded = false; } } } /// /// 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; } /// /// The version of the application to display /// /// The display version. public string DisplayVersion { get { return ApplicationVersion.ToString(); } } /// /// 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 the application paths. /// /// The application paths. public TApplicationPathsType ApplicationPaths { get; private set; } /// /// The _failed assembly loads /// private readonly List _failedPluginAssemblies = new List(); /// /// Gets the plugin assemblies that failed to load. /// /// The failed assembly loads. public IEnumerable FailedPluginAssemblies { get { return _failedPluginAssemblies; } } /// /// Gets the list of currently loaded plugins /// /// The plugins. [ImportMany(typeof(IPlugin))] public IEnumerable Plugins { get; protected set; } /// /// Gets the list of Scheduled Tasks /// /// The scheduled tasks. [ImportMany(typeof(IScheduledTask))] public IEnumerable ScheduledTasks { get; private set; } /// /// Gets the web socket listeners. /// /// The web socket listeners. [ImportMany(typeof(IWebSocketListener))] public IEnumerable WebSocketListeners { get; private set; } /// /// Gets the list of Localized string files /// /// The string files. [ImportMany(typeof(LocalizedStringData))] public IEnumerable StringFiles { get; private set; } /// /// Gets the MEF CompositionContainer /// /// The composition container. private CompositionContainer CompositionContainer { get; set; } /// /// The _HTTP manager /// /// The HTTP manager. public HttpManager HttpManager { get; private set; } /// /// Gets or sets the TCP manager. /// /// The TCP manager. public TcpManager TcpManager { get; private set; } /// /// Gets the task manager. /// /// The task manager. public TaskManager TaskManager { get; private set; } /// /// Gets the iso manager. /// /// The iso manager. public IIsoManager IsoManager { get; private set; } /// /// Gets the rest services. /// /// The rest services. [ImportMany(typeof(IRestfulService))] public IEnumerable RestServices { get; private set; } /// /// The _protobuf serializer initialized /// private bool _protobufSerializerInitialized; /// /// The _protobuf serializer sync lock /// private object _protobufSerializerSyncLock = new object(); /// /// Gets a dynamically compiled generated serializer that can serialize protocontracts without reflection /// private DynamicProtobufSerializer _protobufSerializer; /// /// Gets the protobuf serializer. /// /// The protobuf serializer. public DynamicProtobufSerializer ProtobufSerializer { get { // Lazy load LazyInitializer.EnsureInitialized(ref _protobufSerializer, ref _protobufSerializerInitialized, ref _protobufSerializerSyncLock, () => DynamicProtobufSerializer.Create(Assemblies)); return _protobufSerializer; } private set { _protobufSerializer = value; if (value == null) { _protobufSerializerInitialized = false; } } } /// /// Gets the UDP server port number. /// This can't be configurable because then the user would have to configure their client to discover the server. /// /// The UDP server port number. public abstract int UdpServerPortNumber { get; } /// /// Gets the name of the web application that can be used for url building. /// All api urls will be of the form {protocol}://{host}:{port}/{appname}/... /// /// The name of the web application. public string WebApplicationName { get { return "mediabrowser"; } } /// /// Gets the HTTP server URL prefix. /// /// The HTTP server URL prefix. public virtual string HttpServerUrlPrefix { get { return "http://+:" + Configuration.HttpServerPortNumber + "/" + WebApplicationName + "/"; } } /// /// Gets the kernel context. Subclasses will have to override. /// /// The kernel context. public abstract KernelContext KernelContext { get; } /// /// Gets the log file path. /// /// The log file path. public string LogFilePath { get; private set; } /// /// Gets the logger. /// /// The logger. protected ILogger Logger { get; private set; } /// /// Gets the assemblies. /// /// The assemblies. public Assembly[] Assemblies { get; private set; } /// /// Initializes the Kernel /// /// The iso manager. /// Task. public async Task Init(IIsoManager isoManager) { IsoManager = isoManager; Logger = Logging.LogManager.GetLogger(GetType().Name); ApplicationPaths = new TApplicationPathsType(); IsFirstRun = !File.Exists(ApplicationPaths.SystemConfigurationFilePath); // Performs initializations that can be reloaded at anytime await Reload().ConfigureAwait(false); } /// /// Performs initializations that can be reloaded at anytime /// /// Task. public async Task Reload() { OnReloadBeginning(); await ReloadInternal().ConfigureAwait(false); OnReloadCompleted(); Logger.Info("Kernel.Reload Complete"); } /// /// Performs initializations that can be reloaded at anytime /// /// Task. protected virtual async Task ReloadInternal() { // Set these to null so that they can be lazy loaded again Configuration = null; ProtobufSerializer = null; ReloadLogger(); Logger.Info("Version {0} initializing", ApplicationVersion); DisposeHttpManager(); HttpManager = new HttpManager(this); await OnConfigurationLoaded().ConfigureAwait(false); DisposeTaskManager(); TaskManager = new TaskManager(this); Logger.Info("Loading Plugins"); await ReloadComposableParts().ConfigureAwait(false); DisposeTcpManager(); TcpManager = new TcpManager(this); } /// /// Called when [configuration loaded]. /// /// Task. protected virtual Task OnConfigurationLoaded() { return Task.FromResult(null); } /// /// Disposes and reloads all loggers /// public void ReloadLogger() { DisposeLogger(); LogFilePath = Path.Combine(ApplicationPaths.LogDirectoryPath, KernelContext + "-" + DateTime.Now.Ticks + ".log"); var logFile = new FileTarget(); logFile.FileName = LogFilePath; logFile.Layout = "${longdate}, ${level}, ${logger}, ${message}"; AddLogTarget(logFile, "ApplicationLogFile"); Logging.Logger.LoggerInstance = Logging.LogManager.GetLogger("Global"); OnLoggerLoaded(); } /// /// Adds the log target. /// /// The target. /// The name. private void AddLogTarget(Target target, string name) { var config = LogManager.Configuration; config.RemoveTarget(name); target.Name = name; config.AddTarget(name, target); var level = Configuration.EnableDebugLevelLogging ? LogLevel.Debug : LogLevel.Info; var rule = new LoggingRule("*", level, target); config.LoggingRules.Add(rule); LogManager.Configuration = config; } /// /// Uses MEF to locate plugins /// Subclasses can use this to locate types within plugins /// /// Task. private async Task ReloadComposableParts() { _failedPluginAssemblies.Clear(); DisposeComposableParts(); Assemblies = GetComposablePartAssemblies().ToArray(); CompositionContainer = MefUtils.GetSafeCompositionContainer(Assemblies.Select(i => new AssemblyCatalog(i))); CompositionContainer.ComposeExportedValue("kernel", this); CompositionContainer.ComposeParts(this); await OnComposablePartsLoaded().ConfigureAwait(false); CompositionContainer.Catalog.Dispose(); } /// /// Gets the composable part assemblies. /// /// IEnumerable{Assembly}. protected virtual IEnumerable GetComposablePartAssemblies() { // 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 var pluginAssemblies = Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly) .Select(file => { try { return Assembly.Load(File.ReadAllBytes((file))); } catch (Exception ex) { _failedPluginAssemblies.Add(file); Logger.ErrorException("Error loading {0}", ex, file); return null; } }).Where(a => a != null); foreach (var pluginAssembly in pluginAssemblies) { yield return pluginAssembly; } var runningDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); var corePluginDirectory = Path.Combine(runningDirectory, "CorePlugins"); // This will prevent the .dll file from getting locked, and allow us to replace it when needed pluginAssemblies = Directory.EnumerateFiles(corePluginDirectory, "*.dll", SearchOption.TopDirectoryOnly) .Select(file => { try { return Assembly.Load(File.ReadAllBytes((file))); } catch (Exception ex) { _failedPluginAssemblies.Add(file); Logger.ErrorException("Error loading {0}", ex, file); return null; } }).Where(a => a != null); foreach (var pluginAssembly in pluginAssemblies) { yield return pluginAssembly; } // Include composable parts in the Model assembly yield return typeof (SystemInfo).Assembly; // Include composable parts in the Common assembly yield return Assembly.GetExecutingAssembly(); // Include composable parts in the subclass assembly yield return GetType().Assembly; } /// /// Fires after MEF finishes finding composable parts within plugin assemblies /// /// Task. protected virtual Task OnComposablePartsLoaded() { return Task.Run(() => { foreach (var listener in WebSocketListeners) { listener.Initialize(this); } foreach (var task in ScheduledTasks) { task.Initialize(this); } // Start-up each plugin Parallel.ForEach(Plugins, plugin => { Logger.Info("Initializing {0} {1}", plugin.Name, plugin.Version); try { plugin.Initialize(this); Logger.Info("{0} {1} initialized.", plugin.Name, plugin.Version); } catch (Exception ex) { Logger.ErrorException("Error initializing {0}", ex, plugin.Name); } }); }); } /// /// Notifies that the kernel that a change has been made that requires a restart /// public void NotifyPendingRestart() { HasPendingRestart = true; TcpManager.SendWebSocketMessage("HasPendingRestartChanged", GetSystemInfo()); EventHelper.QueueEventIfNotNull(HasPendingRestartChanged, this, EventArgs.Empty); } /// /// 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 (dispose) { DisposeTcpManager(); DisposeTaskManager(); DisposeIsoManager(); DisposeHttpManager(); DisposeComposableParts(); } } /// /// Disposes the iso manager. /// private void DisposeIsoManager() { if (IsoManager != null) { IsoManager.Dispose(); IsoManager = null; } } /// /// Disposes the TCP manager. /// private void DisposeTcpManager() { if (TcpManager != null) { TcpManager.Dispose(); TcpManager = null; } } /// /// Disposes the task manager. /// private void DisposeTaskManager() { if (TaskManager != null) { TaskManager.Dispose(); TaskManager = null; } } /// /// Disposes the HTTP manager. /// private void DisposeHttpManager() { if (HttpManager != null) { HttpManager.Dispose(); HttpManager = null; } } /// /// Disposes all objects gathered through MEF composable parts /// protected virtual void DisposeComposableParts() { if (CompositionContainer != null) { CompositionContainer.Dispose(); } } /// /// Disposes all logger resources /// private void DisposeLogger() { // Dispose all current loggers var listeners = Trace.Listeners.OfType().ToList(); Trace.Listeners.Clear(); foreach (var listener in listeners) { listener.Dispose(); } } /// /// Gets the current application version /// /// The application version. public Version ApplicationVersion { get { return GetType().Assembly.GetName().Version; } } /// /// Performs the pending restart. /// /// Task. public void PerformPendingRestart() { if (HasPendingRestart) { RestartApplication(); } else { Logger.Info("PerformPendingRestart - not needed"); } } /// /// Restarts the application. /// protected void RestartApplication() { Logger.Info("Restarting the application"); EventHelper.QueueEventIfNotNull(ApplicationRestartRequested, this, EventArgs.Empty); } /// /// Gets the system status. /// /// SystemInfo. public virtual SystemInfo GetSystemInfo() { return new SystemInfo { HasPendingRestart = HasPendingRestart, Version = DisplayVersion, IsNetworkDeployed = ApplicationDeployment.IsNetworkDeployed, WebSocketPortNumber = TcpManager.WebSocketPortNumber, SupportsNativeWebSocket = TcpManager.SupportsNativeWebSocket, FailedPluginAssemblies = FailedPluginAssemblies.ToArray() }; } /// /// The _save lock /// private readonly object _configurationSaveLock = new object(); /// /// Saves the current configuration /// public void SaveConfiguration() { lock (_configurationSaveLock) { XmlSerializer.SerializeToFile(Configuration, ApplicationPaths.SystemConfigurationFilePath); } OnConfigurationUpdated(); } /// /// Gets the application paths. /// /// The application paths. BaseApplicationPaths IKernel.ApplicationPaths { get { return ApplicationPaths; } } /// /// Gets the configuration. /// /// The configuration. BaseApplicationConfiguration IKernel.Configuration { get { return Configuration; } } } }