using MediaBrowser.Common.Events; using MediaBrowser.Common.IO; 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 System; using System.Collections.Generic; 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; #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, () => XmlSerializer.GetXmlConfiguration(ApplicationPaths.SystemConfigurationFilePath, Logger)); 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; } /// /// 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. public IEnumerable Plugins { get; protected set; } /// /// Gets the web socket listeners. /// /// The web socket listeners. public IEnumerable WebSocketListeners { get; private 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 rest services. /// /// The rest services. public IEnumerable RestServices { get; private set; } /// /// The disposable parts /// private readonly List _disposableParts = new List(); /// /// 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(AllTypes)); 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 { 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; } /// /// Gets or sets the task manager. /// /// The task manager. protected ITaskManager TaskManager { get; set; } /// /// Gets the assemblies. /// /// The assemblies. protected Assembly[] Assemblies { get; private set; } /// /// Gets all types. /// /// All types. public Type[] AllTypes { get; private set; } /// /// Initializes a new instance of the class. /// /// The app host. /// The logger. /// isoManager protected BaseKernel(IApplicationHost appHost, ILogger logger) { if (appHost == null) { throw new ArgumentNullException("appHost"); } if (logger == null) { throw new ArgumentNullException("logger"); } ApplicationHost = appHost; Logger = logger; } /// /// Initializes the Kernel /// /// Task. public async Task Init() { 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, Logger); await OnConfigurationLoaded().ConfigureAwait(false); DisposeTaskManager(); TaskManager = new TaskManager(Logger); Logger.Info("Loading Plugins"); await ReloadComposableParts().ConfigureAwait(false); DisposeTcpManager(); TcpManager = new TcpManager(ApplicationHost, this, Logger); } /// /// Called when [configuration loaded]. /// /// Task. protected virtual Task OnConfigurationLoaded() { return Task.FromResult(null); } /// /// Disposes and reloads all loggers /// public void ReloadLogger() { ApplicationHost.ReloadLogger(); OnLoggerLoaded(); } /// /// 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(); AllTypes = Assemblies.SelectMany(GetTypes).ToArray(); ComposeParts(AllTypes); await OnComposablePartsLoaded().ConfigureAwait(false); } /// /// Composes the parts. /// /// All types. private void ComposeParts(IEnumerable allTypes) { var concreteTypes = allTypes.Where(t => t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType).ToArray(); RegisterExportedValues(); FindParts(concreteTypes); } /// /// Composes the parts with ioc container. /// /// All types. protected virtual void FindParts(Type[] allTypes) { RestServices = GetExports(allTypes); WebSocketListeners = GetExports(allTypes); Plugins = GetExports(allTypes); var tasks = GetExports(allTypes, false); TaskManager.AddTasks(tasks); } /// /// Gets the exports. /// /// /// All types. /// if set to true [manage liftime]. /// IEnumerable{``0}. protected IEnumerable GetExports(Type[] allTypes, bool manageLiftime = true) { var currentType = typeof(T); Logger.Info("Composing instances of " + currentType.Name); var parts = allTypes.Where(currentType.IsAssignableFrom).Select(Instantiate).Cast().ToArray(); if (manageLiftime) { _disposableParts.AddRange(parts.OfType()); } return parts; } /// /// Instantiates the specified type. /// /// The type. /// System.Object. private object Instantiate(Type type) { return ApplicationHost.CreateInstance(type); } /// /// Composes the exported values. /// /// The container. protected virtual void RegisterExportedValues() { ApplicationHost.Register(this); ApplicationHost.Register(TaskManager); } /// /// 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; } /// /// 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 private static 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); } } /// /// 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, 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(); DisposeTaskManager(); DisposeHttpManager(); DisposeComposableParts(); _disposableParts.Clear(); } } /// /// 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() { foreach (var part in _disposableParts) { part.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"); 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 = 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; } } } }