BaseKernel.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. using MediaBrowser.Common.Events;
  2. using MediaBrowser.Common.Plugins;
  3. using MediaBrowser.Common.Security;
  4. using MediaBrowser.Model.Configuration;
  5. using MediaBrowser.Model.Logging;
  6. using MediaBrowser.Model.Serialization;
  7. using MediaBrowser.Model.System;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.IO;
  11. using System.Linq;
  12. using System.Threading;
  13. using System.Threading.Tasks;
  14. namespace MediaBrowser.Common.Kernel
  15. {
  16. /// <summary>
  17. /// Represents a shared base kernel for both the Ui and server apps
  18. /// </summary>
  19. /// <typeparam name="TConfigurationType">The type of the T configuration type.</typeparam>
  20. /// <typeparam name="TApplicationPathsType">The type of the T application paths type.</typeparam>
  21. public abstract class BaseKernel<TConfigurationType, TApplicationPathsType> : IDisposable, IKernel
  22. where TConfigurationType : BaseApplicationConfiguration, new()
  23. where TApplicationPathsType : IApplicationPaths
  24. {
  25. #region ConfigurationUpdated Event
  26. /// <summary>
  27. /// Occurs when [configuration updated].
  28. /// </summary>
  29. public event EventHandler<EventArgs> ConfigurationUpdated;
  30. /// <summary>
  31. /// Called when [configuration updated].
  32. /// </summary>
  33. internal void OnConfigurationUpdated()
  34. {
  35. EventHelper.QueueEventIfNotNull(ConfigurationUpdated, this, EventArgs.Empty, Logger);
  36. }
  37. #endregion
  38. #region ReloadCompleted Event
  39. /// <summary>
  40. /// Fires whenever the kernel completes reloading
  41. /// </summary>
  42. public event EventHandler<EventArgs> ReloadCompleted;
  43. /// <summary>
  44. /// Called when [reload completed].
  45. /// </summary>
  46. private void OnReloadCompleted()
  47. {
  48. EventHelper.QueueEventIfNotNull(ReloadCompleted, this, EventArgs.Empty, Logger);
  49. }
  50. #endregion
  51. #region ApplicationUpdated Event
  52. /// <summary>
  53. /// Occurs when [application updated].
  54. /// </summary>
  55. public event EventHandler<GenericEventArgs<Version>> ApplicationUpdated;
  56. /// <summary>
  57. /// Called when [application updated].
  58. /// </summary>
  59. /// <param name="newVersion">The new version.</param>
  60. public void OnApplicationUpdated(Version newVersion)
  61. {
  62. EventHelper.QueueEventIfNotNull(ApplicationUpdated, this, new GenericEventArgs<Version> { Argument = newVersion }, Logger);
  63. NotifyPendingRestart();
  64. }
  65. #endregion
  66. /// <summary>
  67. /// The _configuration loaded
  68. /// </summary>
  69. private bool _configurationLoaded;
  70. /// <summary>
  71. /// The _configuration sync lock
  72. /// </summary>
  73. private object _configurationSyncLock = new object();
  74. /// <summary>
  75. /// The _configuration
  76. /// </summary>
  77. private TConfigurationType _configuration;
  78. /// <summary>
  79. /// Gets the system configuration
  80. /// </summary>
  81. /// <value>The configuration.</value>
  82. public TConfigurationType Configuration
  83. {
  84. get
  85. {
  86. // Lazy load
  87. LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => GetXmlConfiguration<TConfigurationType>(ApplicationPaths.SystemConfigurationFilePath));
  88. return _configuration;
  89. }
  90. protected set
  91. {
  92. _configuration = value;
  93. if (value == null)
  94. {
  95. _configurationLoaded = false;
  96. }
  97. }
  98. }
  99. /// <summary>
  100. /// Gets or sets a value indicating whether this instance has changes that require the entire application to restart.
  101. /// </summary>
  102. /// <value><c>true</c> if this instance has pending application restart; otherwise, <c>false</c>.</value>
  103. public bool HasPendingRestart { get; private set; }
  104. /// <summary>
  105. /// Gets the application paths.
  106. /// </summary>
  107. /// <value>The application paths.</value>
  108. public TApplicationPathsType ApplicationPaths { get; private set; }
  109. /// <summary>
  110. /// Gets the list of currently loaded plugins
  111. /// </summary>
  112. /// <value>The plugins.</value>
  113. public IEnumerable<IPlugin> Plugins { get; protected set; }
  114. /// <summary>
  115. /// Gets or sets the TCP manager.
  116. /// </summary>
  117. /// <value>The TCP manager.</value>
  118. public IServerManager ServerManager { get; private set; }
  119. /// <summary>
  120. /// Gets the plug-in security manager.
  121. /// </summary>
  122. /// <value>The plug-in security manager.</value>
  123. public ISecurityManager SecurityManager { get; set; }
  124. /// <summary>
  125. /// Gets the UDP server port number.
  126. /// This can't be configurable because then the user would have to configure their client to discover the server.
  127. /// </summary>
  128. /// <value>The UDP server port number.</value>
  129. public abstract int UdpServerPortNumber { get; }
  130. /// <summary>
  131. /// Gets the name of the web application that can be used for url building.
  132. /// All api urls will be of the form {protocol}://{host}:{port}/{appname}/...
  133. /// </summary>
  134. /// <value>The name of the web application.</value>
  135. public string WebApplicationName
  136. {
  137. get { return "mediabrowser"; }
  138. }
  139. /// <summary>
  140. /// Gets the HTTP server URL prefix.
  141. /// </summary>
  142. /// <value>The HTTP server URL prefix.</value>
  143. public virtual string HttpServerUrlPrefix
  144. {
  145. get
  146. {
  147. return "http://+:" + Configuration.HttpServerPortNumber + "/" + WebApplicationName + "/";
  148. }
  149. }
  150. /// <summary>
  151. /// Gets the kernel context. Subclasses will have to override.
  152. /// </summary>
  153. /// <value>The kernel context.</value>
  154. public abstract KernelContext KernelContext { get; }
  155. /// <summary>
  156. /// Gets the logger.
  157. /// </summary>
  158. /// <value>The logger.</value>
  159. protected ILogger Logger { get; private set; }
  160. /// <summary>
  161. /// Gets or sets the application host.
  162. /// </summary>
  163. /// <value>The application host.</value>
  164. protected IApplicationHost ApplicationHost { get; private set; }
  165. /// <summary>
  166. /// The _XML serializer
  167. /// </summary>
  168. private readonly IXmlSerializer _xmlSerializer;
  169. /// <summary>
  170. /// Initializes a new instance of the <see cref="BaseKernel{TApplicationPathsType}" /> class.
  171. /// </summary>
  172. /// <param name="appHost">The app host.</param>
  173. /// <param name="appPaths">The app paths.</param>
  174. /// <param name="xmlSerializer">The XML serializer.</param>
  175. /// <param name="logger">The logger.</param>
  176. /// <exception cref="System.ArgumentNullException">isoManager</exception>
  177. protected BaseKernel(IApplicationHost appHost, TApplicationPathsType appPaths, IXmlSerializer xmlSerializer, ILogger logger)
  178. {
  179. ApplicationPaths = appPaths;
  180. ApplicationHost = appHost;
  181. _xmlSerializer = xmlSerializer;
  182. Logger = logger;
  183. }
  184. /// <summary>
  185. /// Initializes the Kernel
  186. /// </summary>
  187. /// <returns>Task.</returns>
  188. public async Task Init()
  189. {
  190. await ReloadInternal().ConfigureAwait(false);
  191. OnReloadCompleted();
  192. Logger.Info("Kernel.Reload Complete");
  193. }
  194. /// <summary>
  195. /// Performs initializations that can be reloaded at anytime
  196. /// </summary>
  197. /// <returns>Task.</returns>
  198. protected virtual async Task ReloadInternal()
  199. {
  200. // Set these to null so that they can be lazy loaded again
  201. Configuration = null;
  202. await OnConfigurationLoaded().ConfigureAwait(false);
  203. FindParts();
  204. await OnComposablePartsLoaded().ConfigureAwait(false);
  205. ServerManager = ApplicationHost.Resolve<IServerManager>();
  206. }
  207. /// <summary>
  208. /// Called when [configuration loaded].
  209. /// </summary>
  210. /// <returns>Task.</returns>
  211. protected virtual Task OnConfigurationLoaded()
  212. {
  213. return Task.FromResult<object>(null);
  214. }
  215. /// <summary>
  216. /// Composes the parts with ioc container.
  217. /// </summary>
  218. protected virtual void FindParts()
  219. {
  220. Plugins = ApplicationHost.GetExports<IPlugin>();
  221. }
  222. /// <summary>
  223. /// Fires after MEF finishes finding composable parts within plugin assemblies
  224. /// </summary>
  225. /// <returns>Task.</returns>
  226. protected virtual Task OnComposablePartsLoaded()
  227. {
  228. return Task.Run(() =>
  229. {
  230. // Start-up each plugin
  231. Parallel.ForEach(Plugins, plugin =>
  232. {
  233. Logger.Info("Initializing {0} {1}", plugin.Name, plugin.Version);
  234. try
  235. {
  236. plugin.Initialize(this, _xmlSerializer, Logger);
  237. Logger.Info("{0} {1} initialized.", plugin.Name, plugin.Version);
  238. }
  239. catch (Exception ex)
  240. {
  241. Logger.ErrorException("Error initializing {0}", ex, plugin.Name);
  242. }
  243. });
  244. });
  245. }
  246. /// <summary>
  247. /// Notifies that the kernel that a change has been made that requires a restart
  248. /// </summary>
  249. public void NotifyPendingRestart()
  250. {
  251. HasPendingRestart = true;
  252. ServerManager.SendWebSocketMessage("HasPendingRestartChanged", GetSystemInfo());
  253. }
  254. /// <summary>
  255. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  256. /// </summary>
  257. public void Dispose()
  258. {
  259. Dispose(true);
  260. GC.SuppressFinalize(this);
  261. }
  262. /// <summary>
  263. /// Releases unmanaged and - optionally - managed resources.
  264. /// </summary>
  265. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  266. protected virtual void Dispose(bool dispose)
  267. {
  268. }
  269. /// <summary>
  270. /// Performs the pending restart.
  271. /// </summary>
  272. /// <returns>Task.</returns>
  273. public void PerformPendingRestart()
  274. {
  275. if (HasPendingRestart)
  276. {
  277. Logger.Info("Restarting the application");
  278. ApplicationHost.Restart();
  279. }
  280. else
  281. {
  282. Logger.Info("PerformPendingRestart - not needed");
  283. }
  284. }
  285. /// <summary>
  286. /// Gets the system status.
  287. /// </summary>
  288. /// <returns>SystemInfo.</returns>
  289. public virtual SystemInfo GetSystemInfo()
  290. {
  291. return new SystemInfo
  292. {
  293. HasPendingRestart = HasPendingRestart,
  294. Version = ApplicationHost.ApplicationVersion.ToString(),
  295. IsNetworkDeployed = ApplicationHost.CanSelfUpdate,
  296. WebSocketPortNumber = ServerManager.WebSocketPortNumber,
  297. SupportsNativeWebSocket = ServerManager.SupportsNativeWebSocket,
  298. FailedPluginAssemblies = ApplicationHost.FailedAssemblies.ToArray()
  299. };
  300. }
  301. /// <summary>
  302. /// The _save lock
  303. /// </summary>
  304. private readonly object _configurationSaveLock = new object();
  305. /// <summary>
  306. /// Saves the current configuration
  307. /// </summary>
  308. public void SaveConfiguration()
  309. {
  310. lock (_configurationSaveLock)
  311. {
  312. _xmlSerializer.SerializeToFile(Configuration, ApplicationPaths.SystemConfigurationFilePath);
  313. }
  314. OnConfigurationUpdated();
  315. }
  316. /// <summary>
  317. /// Gets the application paths.
  318. /// </summary>
  319. /// <value>The application paths.</value>
  320. IApplicationPaths IKernel.ApplicationPaths
  321. {
  322. get { return ApplicationPaths; }
  323. }
  324. /// <summary>
  325. /// Gets the configuration.
  326. /// </summary>
  327. /// <value>The configuration.</value>
  328. BaseApplicationConfiguration IKernel.Configuration
  329. {
  330. get { return Configuration; }
  331. }
  332. /// <summary>
  333. /// Reads an xml configuration file from the file system
  334. /// It will immediately re-serialize and save if new serialization data is available due to property changes
  335. /// </summary>
  336. /// <param name="type">The type.</param>
  337. /// <param name="path">The path.</param>
  338. /// <returns>System.Object.</returns>
  339. public object GetXmlConfiguration(Type type, string path)
  340. {
  341. Logger.Info("Loading {0} at {1}", type.Name, path);
  342. object configuration;
  343. byte[] buffer = null;
  344. // Use try/catch to avoid the extra file system lookup using File.Exists
  345. try
  346. {
  347. buffer = File.ReadAllBytes(path);
  348. configuration = _xmlSerializer.DeserializeFromBytes(type, buffer);
  349. }
  350. catch (FileNotFoundException)
  351. {
  352. configuration = ApplicationHost.CreateInstance(type);
  353. }
  354. // Take the object we just got and serialize it back to bytes
  355. var newBytes = _xmlSerializer.SerializeToBytes(configuration);
  356. // If the file didn't exist before, or if something has changed, re-save
  357. if (buffer == null || !buffer.SequenceEqual(newBytes))
  358. {
  359. Logger.Info("Saving {0} to {1}", type.Name, path);
  360. // Save it after load in case we got new items
  361. File.WriteAllBytes(path, newBytes);
  362. }
  363. return configuration;
  364. }
  365. /// <summary>
  366. /// Reads an xml configuration file from the file system
  367. /// It will immediately save the configuration after loading it, just
  368. /// in case there are new serializable properties
  369. /// </summary>
  370. /// <typeparam name="T"></typeparam>
  371. /// <param name="path">The path.</param>
  372. /// <returns>``0.</returns>
  373. private T GetXmlConfiguration<T>(string path)
  374. where T : class
  375. {
  376. return GetXmlConfiguration(typeof(T), path) as T;
  377. }
  378. /// <summary>
  379. /// Limits simultaneous access to various resources
  380. /// </summary>
  381. /// <value>The resource pools.</value>
  382. public ResourcePool ResourcePools { get; set; }
  383. /// <summary>
  384. /// Removes the plugin.
  385. /// </summary>
  386. /// <param name="plugin">The plugin.</param>
  387. public void RemovePlugin(IPlugin plugin)
  388. {
  389. var list = Plugins.ToList();
  390. list.Remove(plugin);
  391. Plugins = list;
  392. }
  393. }
  394. }