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.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.ComponentModel.Composition.Primitives; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using SimpleInjector; 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. [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. public IEnumerable WebSocketListeners { 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. 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 the assemblies. /// /// The assemblies. public 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 iso manager. /// The logger. /// isoManager protected BaseKernel(IApplicationHost appHost, IIsoManager isoManager, ILogger logger) { if (appHost == null) { throw new ArgumentNullException("appHost"); } if (isoManager == null) { throw new ArgumentNullException("isoManager"); } if (logger == null) { throw new ArgumentNullException("logger"); } ApplicationHost = appHost; IsoManager = isoManager; 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(this, 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); CompositionContainer.Catalog.Dispose(); } /// /// The ioc container /// private readonly Container _iocContainer = new Container(); /// /// Composes the parts. /// /// All types. private void ComposeParts(IEnumerable allTypes) { var concreteTypes = allTypes.Where(t => t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType).ToArray(); CompositionContainer = GetSafeCompositionContainer(concreteTypes.Select(i => new TypeCatalog(i))); ComposeExportedValues(CompositionContainer, _iocContainer); CompositionContainer.ComposeParts(this); ComposePartsWithIocContainer(concreteTypes, _iocContainer); } /// /// Composes the parts with ioc container. /// /// All types. /// The container. protected virtual void ComposePartsWithIocContainer(Type[] allTypes, Container container) { RestServices = GetExports(allTypes); WebSocketListeners = GetExports(allTypes); } /// /// Gets the exports. /// /// /// All types. /// IEnumerable{``0}. protected IEnumerable GetExports(Type[] allTypes) { var currentType = typeof(T); Logger.Info("Composing instances of " + currentType.Name); var parts = allTypes.Where(currentType.IsAssignableFrom).Select(Instantiate).Cast().ToArray(); _disposableParts.AddRange(parts.OfType()); return parts; } /// /// Instantiates the specified type. /// /// The type. /// System.Object. private object Instantiate(Type type) { return _iocContainer.GetInstance(type); } /// /// Composes the exported values. /// /// The container. /// protected virtual void ComposeExportedValues(CompositionContainer container, Container iocContainer) { container.ComposeExportedValue("logger", Logger); container.ComposeExportedValue("appHost", ApplicationHost); iocContainer.RegisterSingle(Logger); iocContainer.RegisterSingle(ApplicationHost); } /// /// 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; } /// /// Plugins that live on both the server and UI are going to have references to assemblies from both sides. /// But looks for Parts on one side, it will throw an exception when it seems Types from the other side that it doesn't have a reference to. /// For example, a plugin provides a Resolver. When MEF runs in the UI, it will throw an exception when it sees the resolver because there won't be a reference to the base class. /// This method will catch those exceptions while retining the list of Types that MEF is able to resolve. /// /// The catalogs. /// CompositionContainer. /// catalogs private static CompositionContainer GetSafeCompositionContainer(IEnumerable catalogs) { if (catalogs == null) { throw new ArgumentNullException("catalogs"); } var newList = new List(); // Go through each Catalog foreach (var catalog in catalogs) { try { // Try to have MEF find Parts catalog.Parts.ToArray(); // If it succeeds we can use the entire catalog newList.Add(catalog); } catch (ReflectionTypeLoadException ex) { // If it fails we can still get a list of the Types it was able to resolve and create TypeCatalogs var typeCatalogs = ex.Types.Where(t => t != null).Select(t => new TypeCatalog(t)); newList.AddRange(typeCatalogs); } } return new CompositionContainer(new AggregateCatalog(newList)); } /// /// 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(() => { foreach (var task in ScheduledTasks) { task.Initialize(this, Logger); } // 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(); DisposeIsoManager(); DisposeHttpManager(); DisposeComposableParts(); foreach (var part in _disposableParts) { part.Dispose(); } _disposableParts.Clear(); } } /// /// 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(); } } /// /// 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; } } } }