123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- using MediaBrowser.Common.IO;
- using MediaBrowser.Model.Logging;
- using MediaBrowser.Model.Net;
- using MediaBrowser.Model.Plugins;
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.IO;
- using System.Linq;
- using System.Threading.Tasks;
- namespace MediaBrowser.UI.Controller
- {
- /// <summary>
- /// This keeps ui plugin assemblies in sync with plugins installed on the server
- /// </summary>
- public class PluginUpdater
- {
- private readonly ILogger _logger;
- public PluginUpdater(ILogger logger)
- {
- _logger = logger;
- }
- /// <summary>
- /// Updates the plugins.
- /// </summary>
- /// <returns>Task{PluginUpdateResult}.</returns>
- public async Task<PluginUpdateResult> UpdatePlugins()
- {
- _logger.Info("Downloading list of installed plugins");
- var allInstalledPlugins = await UIKernel.Instance.ApiClient.GetInstalledPluginsAsync().ConfigureAwait(false);
- var uiPlugins = allInstalledPlugins.Where(p => p.DownloadToUI).ToList();
- var result = new PluginUpdateResult { };
- result.DeletedPlugins = DeleteUninstalledPlugins(uiPlugins);
- await DownloadPluginAssemblies(uiPlugins, result).ConfigureAwait(false);
- result.UpdatedConfigurations = await DownloadPluginConfigurations(uiPlugins).ConfigureAwait(false);
- return result;
- }
- /// <summary>
- /// Downloads plugin assemblies from the server, if they need to be installed or updated.
- /// </summary>
- /// <param name="uiPlugins">The UI plugins.</param>
- /// <param name="result">The result.</param>
- /// <returns>Task.</returns>
- private async Task DownloadPluginAssemblies(IEnumerable<PluginInfo> uiPlugins, PluginUpdateResult result)
- {
- var newlyInstalledPlugins = new List<PluginInfo>();
- var updatedPlugins = new List<PluginInfo>();
- // Loop through the list of plugins that are on the server
- foreach (var pluginInfo in uiPlugins)
- {
- // See if it is already installed in the UI
- var currentAssemblyPath = Path.Combine(UIKernel.Instance.ApplicationPaths.PluginsPath, pluginInfo.AssemblyFileName);
- var isPluginInstalled = File.Exists(currentAssemblyPath);
- // Download the plugin if it is not present, or if the current version is out of date
- bool downloadPlugin;
- if (!isPluginInstalled)
- {
- downloadPlugin = true;
- _logger.Info("{0} is not installed and needs to be downloaded.", pluginInfo.Name);
- }
- else
- {
- var serverVersion = Version.Parse(pluginInfo.Version);
- var fileVersion = FileVersionInfo.GetVersionInfo(currentAssemblyPath).FileVersion ?? string.Empty;
- downloadPlugin = string.IsNullOrEmpty(fileVersion) || Version.Parse(fileVersion) < serverVersion;
- if (downloadPlugin)
- {
- _logger.Info("{0} has an updated version on the server and needs to be downloaded. Server version: {1}, UI version: {2}", pluginInfo.Name, serverVersion, fileVersion);
- }
- }
- if (downloadPlugin)
- {
- if (UIKernel.Instance.ApplicationVersion < Version.Parse(pluginInfo.MinimumRequiredUIVersion))
- {
- _logger.Warn("Can't download new version of {0} because the application needs to be updated first.", pluginInfo.Name);
- continue;
- }
- try
- {
- await DownloadPlugin(pluginInfo).ConfigureAwait(false);
- if (isPluginInstalled)
- {
- updatedPlugins.Add(pluginInfo);
- }
- else
- {
- newlyInstalledPlugins.Add(pluginInfo);
- }
- }
- catch (HttpException ex)
- {
- _logger.ErrorException("Error downloading {0} configuration", ex, pluginInfo.Name);
- }
- catch (IOException ex)
- {
- _logger.ErrorException("Error saving plugin assembly for {0}", ex, pluginInfo.Name);
- }
- }
- }
- result.NewlyInstalledPlugins = newlyInstalledPlugins;
- result.UpdatedPlugins = updatedPlugins;
- }
- /// <summary>
- /// Downloads plugin configurations from the server.
- /// </summary>
- /// <param name="uiPlugins">The UI plugins.</param>
- /// <returns>Task{List{PluginInfo}}.</returns>
- private async Task<List<PluginInfo>> DownloadPluginConfigurations(IEnumerable<PluginInfo> uiPlugins)
- {
- var updatedPlugins = new List<PluginInfo>();
- // Loop through the list of plugins that are on the server
- foreach (var pluginInfo in uiPlugins
- .Where(p => UIKernel.Instance.ApplicationVersion >= Version.Parse(p.MinimumRequiredUIVersion)))
- {
- // See if it is already installed in the UI
- var path = Path.Combine(UIKernel.Instance.ApplicationPaths.PluginConfigurationsPath, pluginInfo.ConfigurationFileName);
- var download = false;
- if (!File.Exists(path))
- {
- download = true;
- _logger.Info("{0} configuration was not found needs to be downloaded.", pluginInfo.Name);
- }
- else if (File.GetLastWriteTimeUtc(path) < pluginInfo.ConfigurationDateLastModified)
- {
- download = true;
- _logger.Info("{0} has an updated configuration on the server and needs to be downloaded.", pluginInfo.Name);
- }
- if (download)
- {
- if (UIKernel.Instance.ApplicationVersion < Version.Parse(pluginInfo.MinimumRequiredUIVersion))
- {
- _logger.Warn("Can't download updated configuration of {0} because the application needs to be updated first.", pluginInfo.Name);
- continue;
- }
-
- try
- {
- await DownloadPluginConfiguration(pluginInfo, path).ConfigureAwait(false);
- updatedPlugins.Add(pluginInfo);
- }
- catch (HttpException ex)
- {
- _logger.ErrorException("Error downloading {0} configuration", ex, pluginInfo.Name);
- }
- catch (IOException ex)
- {
- _logger.ErrorException("Error saving plugin configuration to {0}", ex, path);
- }
- }
- }
- return updatedPlugins;
- }
- /// <summary>
- /// Downloads a plugin assembly from the server
- /// </summary>
- /// <param name="plugin">The plugin.</param>
- /// <returns>Task.</returns>
- private async Task DownloadPlugin(PluginInfo plugin)
- {
- _logger.Info("Downloading {0} Plugin", plugin.Name);
- var path = Path.Combine(UIKernel.Instance.ApplicationPaths.PluginsPath, plugin.AssemblyFileName);
- // First download to a MemoryStream. This way if the download is cut off, we won't be left with a partial file
- using (var memoryStream = new MemoryStream())
- {
- var assemblyStream = await UIKernel.Instance.ApiClient.GetPluginAssemblyAsync(plugin).ConfigureAwait(false);
- await assemblyStream.CopyToAsync(memoryStream).ConfigureAwait(false);
- memoryStream.Position = 0;
- using (var fileStream = new FileStream(path, FileMode.Create))
- {
- await memoryStream.CopyToAsync(fileStream).ConfigureAwait(false);
- }
- }
- }
- /// <summary>
- /// Downloads the latest configuration for a plugin
- /// </summary>
- /// <param name="pluginInfo">The plugin info.</param>
- /// <param name="path">The path.</param>
- /// <returns>Task.</returns>
- private async Task DownloadPluginConfiguration(PluginInfo pluginInfo, string path)
- {
- _logger.Info("Downloading {0} Configuration", pluginInfo.Name);
- // First download to a MemoryStream. This way if the download is cut off, we won't be left with a partial file
- using (var stream = await UIKernel.Instance.ApiClient.GetPluginConfigurationFileAsync(pluginInfo.Id).ConfigureAwait(false))
- {
- using (var memoryStream = new MemoryStream())
- {
- await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
- memoryStream.Position = 0;
- using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, true))
- {
- await memoryStream.CopyToAsync(fs).ConfigureAwait(false);
- }
- }
- }
- File.SetLastWriteTimeUtc(path, pluginInfo.ConfigurationDateLastModified);
- }
- /// <summary>
- /// Deletes any plugins that have been uninstalled from the server
- /// </summary>
- /// <param name="uiPlugins">The UI plugins.</param>
- /// <returns>IEnumerable{System.String}.</returns>
- private IEnumerable<string> DeleteUninstalledPlugins(IEnumerable<PluginInfo> uiPlugins)
- {
- var deletedPlugins = new List<string>();
- foreach (var plugin in Directory.EnumerateFiles(UIKernel.Instance.ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly)
- .Select(Path.GetFileName)
- .ToList())
- {
- var serverPlugin = uiPlugins.FirstOrDefault(p => p.AssemblyFileName.Equals(plugin, StringComparison.OrdinalIgnoreCase));
- if (serverPlugin == null)
- {
- try
- {
- DeletePlugin(plugin);
- deletedPlugins.Add(plugin);
- }
- catch (IOException ex)
- {
- _logger.ErrorException("Error deleting plugin assembly {0}", ex, plugin);
- }
- }
- }
- return deletedPlugins;
- }
- /// <summary>
- /// Deletes an installed ui plugin.
- /// Leaves config and data behind in the event it is later re-installed
- /// </summary>
- /// <param name="plugin">The plugin.</param>
- private void DeletePlugin(string plugin)
- {
- _logger.Info("Deleting {0} Plugin", plugin);
- if (File.Exists(plugin))
- {
- File.Delete(plugin);
- }
- }
- }
- /// <summary>
- /// Class PluginUpdateResult
- /// </summary>
- public class PluginUpdateResult
- {
- /// <summary>
- /// Gets or sets the deleted plugins.
- /// </summary>
- /// <value>The deleted plugins.</value>
- public IEnumerable<string> DeletedPlugins { get; set; }
- /// <summary>
- /// Gets or sets the newly installed plugins.
- /// </summary>
- /// <value>The newly installed plugins.</value>
- public IEnumerable<PluginInfo> NewlyInstalledPlugins { get; set; }
- /// <summary>
- /// Gets or sets the updated plugins.
- /// </summary>
- /// <value>The updated plugins.</value>
- public IEnumerable<PluginInfo> UpdatedPlugins { get; set; }
- /// <summary>
- /// Gets or sets the updated configurations.
- /// </summary>
- /// <value>The updated configurations.</value>
- public IEnumerable<PluginInfo> UpdatedConfigurations { get; set; }
- }
- }
|