BasePlugin.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. using MediaBrowser.Common.Kernel;
  2. using MediaBrowser.Common.Logging;
  3. using MediaBrowser.Common.Serialization;
  4. using MediaBrowser.Model.Logging;
  5. using MediaBrowser.Model.Plugins;
  6. using System;
  7. using System.IO;
  8. using System.Reflection;
  9. using System.Runtime.InteropServices;
  10. using System.Threading;
  11. namespace MediaBrowser.Common.Plugins
  12. {
  13. /// <summary>
  14. /// Provides a common base class for all plugins
  15. /// </summary>
  16. /// <typeparam name="TConfigurationType">The type of the T configuration type.</typeparam>
  17. public abstract class BasePlugin<TConfigurationType> : IDisposable, IPlugin
  18. where TConfigurationType : BasePluginConfiguration
  19. {
  20. /// <summary>
  21. /// Gets the kernel.
  22. /// </summary>
  23. /// <value>The kernel.</value>
  24. protected IKernel Kernel { get; private set; }
  25. /// <summary>
  26. /// Gets or sets the plugin's current context
  27. /// </summary>
  28. /// <value>The context.</value>
  29. protected KernelContext Context { get { return Kernel.KernelContext; } }
  30. /// <summary>
  31. /// Gets the name of the plugin
  32. /// </summary>
  33. /// <value>The name.</value>
  34. public abstract string Name { get; }
  35. /// <summary>
  36. /// Gets the description.
  37. /// </summary>
  38. /// <value>The description.</value>
  39. public virtual string Description
  40. {
  41. get { return string.Empty; }
  42. }
  43. /// <summary>
  44. /// Gets a value indicating whether this instance is a core plugin.
  45. /// </summary>
  46. /// <value><c>true</c> if this instance is a core plugin; otherwise, <c>false</c>.</value>
  47. public virtual bool IsCorePlugin
  48. {
  49. get
  50. {
  51. return false;
  52. }
  53. }
  54. /// <summary>
  55. /// Gets the type of configuration this plugin uses
  56. /// </summary>
  57. /// <value>The type of the configuration.</value>
  58. public Type ConfigurationType
  59. {
  60. get { return typeof(TConfigurationType); }
  61. }
  62. /// <summary>
  63. /// The _assembly name
  64. /// </summary>
  65. private AssemblyName _assemblyName;
  66. /// <summary>
  67. /// Gets the name of the assembly.
  68. /// </summary>
  69. /// <value>The name of the assembly.</value>
  70. protected AssemblyName AssemblyName
  71. {
  72. get
  73. {
  74. return _assemblyName ?? (_assemblyName = GetType().Assembly.GetName());
  75. }
  76. }
  77. /// <summary>
  78. /// The _unique id
  79. /// </summary>
  80. private Guid? _uniqueId;
  81. /// <summary>
  82. /// Gets the unique id.
  83. /// </summary>
  84. /// <value>The unique id.</value>
  85. public Guid UniqueId
  86. {
  87. get
  88. {
  89. if (!_uniqueId.HasValue)
  90. {
  91. _uniqueId = Marshal.GetTypeLibGuidForAssembly(GetType().Assembly);
  92. }
  93. return _uniqueId.Value;
  94. }
  95. }
  96. /// <summary>
  97. /// Gets the plugin version
  98. /// </summary>
  99. /// <value>The version.</value>
  100. public Version Version
  101. {
  102. get
  103. {
  104. return AssemblyName.Version;
  105. }
  106. }
  107. /// <summary>
  108. /// Gets the name the assembly file
  109. /// </summary>
  110. /// <value>The name of the assembly file.</value>
  111. public string AssemblyFileName
  112. {
  113. get
  114. {
  115. return AssemblyName.Name + ".dll";
  116. }
  117. }
  118. /// <summary>
  119. /// Gets the last date modified of the configuration
  120. /// </summary>
  121. /// <value>The configuration date last modified.</value>
  122. public DateTime ConfigurationDateLastModified
  123. {
  124. get
  125. {
  126. // Ensure it's been lazy loaded
  127. var config = Configuration;
  128. return File.GetLastWriteTimeUtc(ConfigurationFilePath);
  129. }
  130. }
  131. /// <summary>
  132. /// Gets the last date modified of the plugin
  133. /// </summary>
  134. /// <value>The assembly date last modified.</value>
  135. public DateTime AssemblyDateLastModified
  136. {
  137. get
  138. {
  139. return File.GetLastWriteTimeUtc(AssemblyFilePath);
  140. }
  141. }
  142. /// <summary>
  143. /// Gets the path to the assembly file
  144. /// </summary>
  145. /// <value>The assembly file path.</value>
  146. public string AssemblyFilePath
  147. {
  148. get
  149. {
  150. return Path.Combine(Kernel.ApplicationPaths.PluginsPath, AssemblyFileName);
  151. }
  152. }
  153. /// <summary>
  154. /// The _configuration sync lock
  155. /// </summary>
  156. private object _configurationSyncLock = new object();
  157. /// <summary>
  158. /// The _configuration initialized
  159. /// </summary>
  160. private bool _configurationInitialized;
  161. /// <summary>
  162. /// The _configuration
  163. /// </summary>
  164. private TConfigurationType _configuration;
  165. /// <summary>
  166. /// Gets the plugin's configuration
  167. /// </summary>
  168. /// <value>The configuration.</value>
  169. public TConfigurationType Configuration
  170. {
  171. get
  172. {
  173. // Lazy load
  174. LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationInitialized, ref _configurationSyncLock, () => XmlSerializer.GetXmlConfiguration(ConfigurationType, ConfigurationFilePath) as TConfigurationType);
  175. return _configuration;
  176. }
  177. protected set
  178. {
  179. _configuration = value;
  180. if (value == null)
  181. {
  182. _configurationInitialized = false;
  183. }
  184. }
  185. }
  186. /// <summary>
  187. /// Gets the name of the configuration file. Subclasses should override
  188. /// </summary>
  189. /// <value>The name of the configuration file.</value>
  190. public virtual string ConfigurationFileName
  191. {
  192. get { return Path.ChangeExtension(AssemblyFileName, ".xml"); }
  193. }
  194. /// <summary>
  195. /// Gets the full path to the configuration file
  196. /// </summary>
  197. /// <value>The configuration file path.</value>
  198. public string ConfigurationFilePath
  199. {
  200. get
  201. {
  202. return Path.Combine(Kernel.ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName);
  203. }
  204. }
  205. /// <summary>
  206. /// The _data folder path
  207. /// </summary>
  208. private string _dataFolderPath;
  209. /// <summary>
  210. /// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed
  211. /// </summary>
  212. /// <value>The data folder path.</value>
  213. public string DataFolderPath
  214. {
  215. get
  216. {
  217. if (_dataFolderPath == null)
  218. {
  219. // Give the folder name the same name as the config file name
  220. // We can always make this configurable if/when needed
  221. _dataFolderPath = Path.Combine(Kernel.ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(ConfigurationFileName));
  222. if (!Directory.Exists(_dataFolderPath))
  223. {
  224. Directory.CreateDirectory(_dataFolderPath);
  225. }
  226. }
  227. return _dataFolderPath;
  228. }
  229. }
  230. /// <summary>
  231. /// Returns true or false indicating if the plugin should be downloaded and run within the Ui.
  232. /// </summary>
  233. /// <value><c>true</c> if [download to UI]; otherwise, <c>false</c>.</value>
  234. public virtual bool DownloadToUi
  235. {
  236. get
  237. {
  238. return false;
  239. }
  240. }
  241. /// <summary>
  242. /// Gets the logger.
  243. /// </summary>
  244. /// <value>The logger.</value>
  245. public ILogger Logger { get; private set; }
  246. /// <summary>
  247. /// Starts the plugin.
  248. /// </summary>
  249. /// <param name="kernel">The kernel.</param>
  250. /// <exception cref="System.ArgumentNullException">kernel</exception>
  251. public void Initialize(IKernel kernel)
  252. {
  253. if (kernel == null)
  254. {
  255. throw new ArgumentNullException("kernel");
  256. }
  257. Logger = LogManager.GetLogger(Name);
  258. Kernel = kernel;
  259. if (kernel.KernelContext == KernelContext.Server)
  260. {
  261. InitializeOnServer(!File.Exists(ConfigurationFilePath));
  262. }
  263. else if (kernel.KernelContext == KernelContext.Ui)
  264. {
  265. InitializeInUi();
  266. }
  267. }
  268. /// <summary>
  269. /// Starts the plugin on the server
  270. /// </summary>
  271. /// <param name="isFirstRun">if set to <c>true</c> [is first run].</param>
  272. protected virtual void InitializeOnServer(bool isFirstRun)
  273. {
  274. }
  275. /// <summary>
  276. /// Starts the plugin in the Ui
  277. /// </summary>
  278. protected virtual void InitializeInUi()
  279. {
  280. }
  281. /// <summary>
  282. /// Disposes the plugins. Undos all actions performed during Init.
  283. /// </summary>
  284. public void Dispose()
  285. {
  286. Dispose(true);
  287. GC.SuppressFinalize(this);
  288. }
  289. /// <summary>
  290. /// Releases unmanaged and - optionally - managed resources.
  291. /// </summary>
  292. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  293. protected void Dispose(bool dispose)
  294. {
  295. if (Kernel.KernelContext == KernelContext.Server)
  296. {
  297. DisposeOnServer(dispose);
  298. }
  299. else if (Kernel.KernelContext == KernelContext.Ui)
  300. {
  301. DisposeInUI(dispose);
  302. }
  303. }
  304. /// <summary>
  305. /// Releases unmanaged and - optionally - managed resources.
  306. /// </summary>
  307. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  308. protected virtual void DisposeOnServer(bool dispose)
  309. {
  310. }
  311. /// <summary>
  312. /// Releases unmanaged and - optionally - managed resources.
  313. /// </summary>
  314. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  315. protected virtual void DisposeInUI(bool dispose)
  316. {
  317. }
  318. /// <summary>
  319. /// The _save lock
  320. /// </summary>
  321. private readonly object _configurationSaveLock = new object();
  322. /// <summary>
  323. /// Saves the current configuration to the file system
  324. /// </summary>
  325. /// <exception cref="System.InvalidOperationException">Cannot call Plugin.SaveConfiguration from the UI.</exception>
  326. public virtual void SaveConfiguration()
  327. {
  328. if (Kernel.KernelContext != KernelContext.Server)
  329. {
  330. throw new InvalidOperationException("Cannot call Plugin.SaveConfiguration from the UI.");
  331. }
  332. Logger.Info("Saving configuration");
  333. lock (_configurationSaveLock)
  334. {
  335. XmlSerializer.SerializeToFile(Configuration, ConfigurationFilePath);
  336. }
  337. // Notify connected UI's
  338. Kernel.TcpManager.SendWebSocketMessage("PluginConfigurationUpdated-" + Name, Configuration);
  339. }
  340. /// <summary>
  341. /// Completely overwrites the current configuration with a new copy
  342. /// Returns true or false indicating success or failure
  343. /// </summary>
  344. /// <param name="configuration">The configuration.</param>
  345. /// <exception cref="System.ArgumentNullException">configuration</exception>
  346. public virtual void UpdateConfiguration(BasePluginConfiguration configuration)
  347. {
  348. if (configuration == null)
  349. {
  350. throw new ArgumentNullException("configuration");
  351. }
  352. Configuration = (TConfigurationType)configuration;
  353. SaveConfiguration();
  354. }
  355. /// <summary>
  356. /// Gets the plugin info.
  357. /// </summary>
  358. /// <returns>PluginInfo.</returns>
  359. public PluginInfo GetPluginInfo()
  360. {
  361. var info = new PluginInfo
  362. {
  363. Name = Name,
  364. DownloadToUI = DownloadToUi,
  365. Version = Version.ToString(),
  366. AssemblyFileName = AssemblyFileName,
  367. ConfigurationDateLastModified = ConfigurationDateLastModified,
  368. Description = Description,
  369. IsCorePlugin = IsCorePlugin,
  370. UniqueId = UniqueId,
  371. EnableAutoUpdate = Configuration.EnableAutoUpdate,
  372. UpdateClass = Configuration.UpdateClass,
  373. ConfigurationFileName = ConfigurationFileName
  374. };
  375. var uiPlugin = this as IUIPlugin;
  376. if (uiPlugin != null)
  377. {
  378. info.MinimumRequiredUIVersion = uiPlugin.MinimumRequiredUIVersion.ToString();
  379. }
  380. return info;
  381. }
  382. /// <summary>
  383. /// Called when just before the plugin is uninstalled from the server.
  384. /// </summary>
  385. public virtual void OnUninstalling()
  386. {
  387. }
  388. /// <summary>
  389. /// Gets the plugin's configuration
  390. /// </summary>
  391. /// <value>The configuration.</value>
  392. BasePluginConfiguration IPlugin.Configuration
  393. {
  394. get { return Configuration; }
  395. }
  396. }
  397. }