PluginUpdater.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. using MediaBrowser.Common.Logging;
  2. using MediaBrowser.Common.Plugins;
  3. using MediaBrowser.Common.Serialization;
  4. using MediaBrowser.Model.DTO;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.ComponentModel.Composition;
  8. using System.ComponentModel.Composition.Hosting;
  9. using System.IO;
  10. using System.Linq;
  11. using System.Threading.Tasks;
  12. namespace MediaBrowser.UI.Controller
  13. {
  14. /// <summary>
  15. /// This keeps ui plugin assemblies in sync with plugins installed on the server
  16. /// </summary>
  17. public class PluginUpdater
  18. {
  19. /// <summary>
  20. /// Gets the list of currently installed UI plugins
  21. /// </summary>
  22. [ImportMany(typeof(BasePlugin))]
  23. private IEnumerable<BasePlugin> CurrentPlugins { get; set; }
  24. private CompositionContainer CompositionContainer { get; set; }
  25. public async Task<PluginUpdateResult> UpdatePlugins()
  26. {
  27. // First load the plugins that are currently installed
  28. ReloadComposableParts();
  29. Logger.LogInfo("Downloading list of installed plugins");
  30. PluginInfo[] allInstalledPlugins = await UIKernel.Instance.ApiClient.GetInstalledPluginsAsync().ConfigureAwait(false);
  31. IEnumerable<PluginInfo> uiPlugins = allInstalledPlugins.Where(p => p.DownloadToUI);
  32. PluginUpdateResult result = new PluginUpdateResult();
  33. result.DeletedPlugins = DeleteUninstalledPlugins(uiPlugins);
  34. await DownloadPluginAssemblies(uiPlugins, result).ConfigureAwait(false);
  35. // If any new assemblies were downloaded we'll have to reload the CurrentPlugins list
  36. if (result.NewlyInstalledPlugins.Any())
  37. {
  38. ReloadComposableParts();
  39. }
  40. result.UpdatedConfigurations = await DownloadPluginConfigurations(uiPlugins).ConfigureAwait(false);
  41. CompositionContainer.Dispose();
  42. return result;
  43. }
  44. /// <summary>
  45. /// Downloads plugin assemblies from the server, if they need to be installed or updated.
  46. /// </summary>
  47. private async Task DownloadPluginAssemblies(IEnumerable<PluginInfo> uiPlugins, PluginUpdateResult result)
  48. {
  49. List<PluginInfo> newlyInstalledPlugins = new List<PluginInfo>();
  50. List<PluginInfo> updatedPlugins = new List<PluginInfo>();
  51. // Loop through the list of plugins that are on the server
  52. foreach (PluginInfo pluginInfo in uiPlugins)
  53. {
  54. // See if it is already installed in the UI
  55. BasePlugin installedPlugin = CurrentPlugins.FirstOrDefault(p => p.AssemblyFileName.Equals(pluginInfo.AssemblyFileName, StringComparison.OrdinalIgnoreCase));
  56. // Download the plugin if it is not present, or if the current version is out of date
  57. bool downloadPlugin = installedPlugin == null;
  58. if (installedPlugin != null)
  59. {
  60. Version serverVersion = Version.Parse(pluginInfo.Version);
  61. downloadPlugin = serverVersion > installedPlugin.Version;
  62. }
  63. if (downloadPlugin)
  64. {
  65. await DownloadPlugin(pluginInfo).ConfigureAwait(false);
  66. if (installedPlugin == null)
  67. {
  68. newlyInstalledPlugins.Add(pluginInfo);
  69. }
  70. else
  71. {
  72. updatedPlugins.Add(pluginInfo);
  73. }
  74. }
  75. }
  76. result.NewlyInstalledPlugins = newlyInstalledPlugins;
  77. result.UpdatedPlugins = updatedPlugins;
  78. }
  79. /// <summary>
  80. /// Downloads plugin configurations from the server.
  81. /// </summary>
  82. private async Task<List<PluginInfo>> DownloadPluginConfigurations(IEnumerable<PluginInfo> uiPlugins)
  83. {
  84. List<PluginInfo> updatedPlugins = new List<PluginInfo>();
  85. // Loop through the list of plugins that are on the server
  86. foreach (PluginInfo pluginInfo in uiPlugins)
  87. {
  88. // See if it is already installed in the UI
  89. BasePlugin installedPlugin = CurrentPlugins.First(p => p.AssemblyFileName.Equals(pluginInfo.AssemblyFileName, StringComparison.OrdinalIgnoreCase));
  90. if (installedPlugin.ConfigurationDateLastModified < pluginInfo.ConfigurationDateLastModified)
  91. {
  92. await DownloadPluginConfiguration(installedPlugin, pluginInfo).ConfigureAwait(false);
  93. updatedPlugins.Add(pluginInfo);
  94. }
  95. }
  96. return updatedPlugins;
  97. }
  98. /// <summary>
  99. /// Downloads a plugin assembly from the server
  100. /// </summary>
  101. private async Task DownloadPlugin(PluginInfo plugin)
  102. {
  103. Logger.LogInfo("Downloading {0} Plugin", plugin.Name);
  104. string path = Path.Combine(UIKernel.Instance.ApplicationPaths.PluginsPath, plugin.AssemblyFileName);
  105. // First download to a MemoryStream. This way if the download is cut off, we won't be left with a partial file
  106. using (MemoryStream memoryStream = new MemoryStream())
  107. {
  108. Stream assemblyStream = await UIKernel.Instance.ApiClient.GetPluginAssemblyAsync(plugin).ConfigureAwait(false);
  109. await assemblyStream.CopyToAsync(memoryStream).ConfigureAwait(false);
  110. memoryStream.Position = 0;
  111. using (FileStream fileStream = new FileStream(path, FileMode.Create))
  112. {
  113. await memoryStream.CopyToAsync(fileStream).ConfigureAwait(false);
  114. }
  115. }
  116. }
  117. /// <summary>
  118. /// Downloads the latest configuration for a plugin
  119. /// </summary>
  120. private async Task DownloadPluginConfiguration(BasePlugin plugin, PluginInfo pluginInfo)
  121. {
  122. Logger.LogInfo("Downloading {0} Configuration", plugin.Name);
  123. object config = await UIKernel.Instance.ApiClient.GetPluginConfigurationAsync(pluginInfo, plugin.ConfigurationType).ConfigureAwait(false);
  124. XmlSerializer.SerializeToFile(config, plugin.ConfigurationFilePath);
  125. File.SetLastWriteTimeUtc(plugin.ConfigurationFilePath, pluginInfo.ConfigurationDateLastModified);
  126. }
  127. /// <summary>
  128. /// Deletes any plugins that have been uninstalled from the server
  129. /// </summary>
  130. private IEnumerable<string> DeleteUninstalledPlugins(IEnumerable<PluginInfo> uiPlugins)
  131. {
  132. var deletedPlugins = new List<string>();
  133. foreach (BasePlugin plugin in CurrentPlugins)
  134. {
  135. PluginInfo latest = uiPlugins.FirstOrDefault(p => p.AssemblyFileName.Equals(plugin.AssemblyFileName, StringComparison.OrdinalIgnoreCase));
  136. if (latest == null)
  137. {
  138. DeletePlugin(plugin);
  139. deletedPlugins.Add(plugin.Name);
  140. }
  141. }
  142. return deletedPlugins;
  143. }
  144. /// <summary>
  145. /// Deletes an installed ui plugin.
  146. /// Leaves config and data behind in the event it is later re-installed
  147. /// </summary>
  148. private void DeletePlugin(BasePlugin plugin)
  149. {
  150. Logger.LogInfo("Deleting {0} Plugin", plugin.Name);
  151. string path = plugin.AssemblyFilePath;
  152. if (File.Exists(path))
  153. {
  154. File.Delete(path);
  155. }
  156. }
  157. /// <summary>
  158. /// Re-uses MEF within the kernel to discover installed plugins
  159. /// </summary>
  160. private void ReloadComposableParts()
  161. {
  162. if (CompositionContainer != null)
  163. {
  164. CompositionContainer.Dispose();
  165. }
  166. CompositionContainer = UIKernel.Instance.GetCompositionContainer();
  167. CompositionContainer.ComposeParts(this);
  168. CompositionContainer.Catalog.Dispose();
  169. foreach (BasePlugin plugin in CurrentPlugins)
  170. {
  171. plugin.Initialize(UIKernel.Instance, false);
  172. }
  173. }
  174. }
  175. public class PluginUpdateResult
  176. {
  177. public IEnumerable<string> DeletedPlugins { get; set; }
  178. public IEnumerable<PluginInfo> NewlyInstalledPlugins { get; set; }
  179. public IEnumerable<PluginInfo> UpdatedPlugins { get; set; }
  180. public IEnumerable<PluginInfo> UpdatedConfigurations { get; set; }
  181. }
  182. }