using MediaBrowser.Common.Events; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.System; using System; using System.Collections.Generic; using System.IO; using System.Linq; 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 : IApplicationPaths { /// /// Occurs when [has pending restart changed]. /// public event EventHandler HasPendingRestartChanged; #region ConfigurationUpdated Event /// /// Occurs when [configuration updated]. /// public event EventHandler ConfigurationUpdated; /// /// Called when [configuration updated]. /// internal void OnConfigurationUpdated() { EventHelper.QueueEventIfNotNull(ConfigurationUpdated, this, EventArgs.Empty, Logger); // 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, Logger); } #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, Logger); } #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, Logger); } #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 }, Logger); 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, () => 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; } /// /// 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; } /// /// Gets the list of currently loaded plugins /// /// The plugins. public IEnumerable Plugins { get; protected set; } /// /// Gets the web socket listeners. /// /// The web socket listeners. public IEnumerable WebSocketListeners { get; private set; } /// /// Gets or sets the TCP manager. /// /// The TCP manager. public TcpManager TcpManager { get; private set; } /// /// Gets the rest services. /// /// The rest services. public IEnumerable RestServices { get; private set; } /// /// 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 { return ApplicationHost.LogFilePath; } } /// /// Gets the logger. /// /// The logger. protected ILogger Logger { get; private set; } /// /// Gets or sets the application host. /// /// The application host. protected IApplicationHost ApplicationHost { get; private set; } /// /// The _XML serializer /// private readonly IXmlSerializer _xmlSerializer; /// /// Initializes a new instance of the class. /// /// The app host. /// The app paths. /// The XML serializer. /// The logger. /// isoManager protected BaseKernel(IApplicationHost appHost, TApplicationPathsType appPaths, IXmlSerializer xmlSerializer, ILogger logger) { if (appHost == null) { throw new ArgumentNullException("appHost"); } if (appPaths == null) { throw new ArgumentNullException("appPaths"); } if (xmlSerializer == null) { throw new ArgumentNullException("xmlSerializer"); } if (logger == null) { throw new ArgumentNullException("logger"); } ApplicationPaths = appPaths; ApplicationHost = appHost; _xmlSerializer = xmlSerializer; Logger = logger; } /// /// Initializes the Kernel /// /// Task. public Task Init() { IsFirstRun = !File.Exists(ApplicationPaths.SystemConfigurationFilePath); // Performs initializations that can be reloaded at anytime return Reload(); } /// /// 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; ReloadLogger(); Logger.Info("Version {0} initializing", ApplicationVersion); await OnConfigurationLoaded().ConfigureAwait(false); FindParts(); await OnComposablePartsLoaded().ConfigureAwait(false); DisposeTcpManager(); TcpManager = (TcpManager)ApplicationHost.CreateInstance(typeof(TcpManager)); } /// /// Called when [configuration loaded]. /// /// Task. protected virtual Task OnConfigurationLoaded() { return Task.FromResult(null); } /// /// Disposes and reloads all loggers /// public void ReloadLogger() { ApplicationHost.ReloadLogger(); OnLoggerLoaded(); } /// /// Composes the parts with ioc container. /// protected virtual void FindParts() { RestServices = ApplicationHost.GetExports(); WebSocketListeners = ApplicationHost.GetExports(); Plugins = ApplicationHost.GetExports(); } /// /// Fires after MEF finishes finding composable parts within plugin assemblies /// /// Task. protected virtual Task OnComposablePartsLoaded() { return Task.Run(() => { // Start-up each plugin Parallel.ForEach(Plugins, plugin => { Logger.Info("Initializing {0} {1}", plugin.Name, plugin.Version); try { plugin.Initialize(this, _xmlSerializer, Logger); 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, Logger); } /// /// 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(); } } /// /// Disposes the TCP manager. /// private void DisposeTcpManager() { if (TcpManager != null) { TcpManager.Dispose(); TcpManager = null; } } /// /// 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"); ApplicationHost.Restart(); } /// /// Gets the system status. /// /// SystemInfo. public virtual SystemInfo GetSystemInfo() { return new SystemInfo { HasPendingRestart = HasPendingRestart, Version = ApplicationVersion.ToString(), IsNetworkDeployed = ApplicationHost.CanSelfUpdate, WebSocketPortNumber = TcpManager.WebSocketPortNumber, SupportsNativeWebSocket = TcpManager.SupportsNativeWebSocket, FailedPluginAssemblies = ApplicationHost.FailedAssemblies.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. IApplicationPaths IKernel.ApplicationPaths { get { return ApplicationPaths; } } /// /// Gets the configuration. /// /// The configuration. BaseApplicationConfiguration IKernel.Configuration { get { return Configuration; } } /// /// Reads an xml configuration file from the file system /// It will immediately re-serialize and save if new serialization data is available due to property changes /// /// The type. /// The path. /// System.Object. public object GetXmlConfiguration(Type type, string path) { Logger.Info("Loading {0} at {1}", type.Name, path); object configuration; byte[] buffer = null; // Use try/catch to avoid the extra file system lookup using File.Exists try { buffer = File.ReadAllBytes(path); configuration = _xmlSerializer.DeserializeFromBytes(type, buffer); } catch (FileNotFoundException) { configuration = ApplicationHost.CreateInstance(type); } // Take the object we just got and serialize it back to bytes var newBytes = _xmlSerializer.SerializeToBytes(configuration); // If the file didn't exist before, or if something has changed, re-save if (buffer == null || !buffer.SequenceEqual(newBytes)) { Logger.Info("Saving {0} to {1}", type.Name, path); // Save it after load in case we got new items File.WriteAllBytes(path, newBytes); } return configuration; } /// /// Reads an xml configuration file from the file system /// It will immediately save the configuration after loading it, just /// in case there are new serializable properties /// /// /// The path. /// ``0. private T GetXmlConfiguration(string path) where T : class { return GetXmlConfiguration(typeof(T), path) as T; } } }