307 lines
12 KiB
C#
307 lines
12 KiB
C#
using MediaBrowser.Common.IO;
|
|
using MediaBrowser.Common.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
|
|
{
|
|
/// <summary>
|
|
/// Updates the plugins.
|
|
/// </summary>
|
|
/// <returns>Task{PluginUpdateResult}.</returns>
|
|
public async Task<PluginUpdateResult> UpdatePlugins()
|
|
{
|
|
Logger.LogInfo("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.LogInfo("{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.LogInfo("{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.LogWarning("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.LogException("Error downloading {0} configuration", ex, pluginInfo.Name);
|
|
}
|
|
catch (IOException ex)
|
|
{
|
|
Logger.LogException("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.LogInfo("{0} configuration was not found needs to be downloaded.", pluginInfo.Name);
|
|
}
|
|
else if (File.GetLastWriteTimeUtc(path) < pluginInfo.ConfigurationDateLastModified)
|
|
{
|
|
download = true;
|
|
Logger.LogInfo("{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.LogWarning("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.LogException("Error downloading {0} configuration", ex, pluginInfo.Name);
|
|
}
|
|
catch (IOException ex)
|
|
{
|
|
Logger.LogException("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.LogInfo("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.LogInfo("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.UniqueId).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.LogException("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.LogInfo("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; }
|
|
}
|
|
}
|