254 lines
12 KiB
C#
254 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Web;
|
|
using Disco.Data.Repository;
|
|
using Disco.Models.BI.Interop.Community;
|
|
using Disco.Services.Tasks;
|
|
using Quartz;
|
|
|
|
namespace Disco.Services.Plugins
|
|
{
|
|
public class UpdatePluginTask : ScheduledTask
|
|
{
|
|
public override string TaskName { get { return "Updating Plugin/s"; } }
|
|
|
|
protected override void ExecuteTask()
|
|
{
|
|
string pluginId = (string)this.ExecutionContext.JobDetail.JobDataMap["PluginId"];
|
|
string packageFilePath = (string)this.ExecutionContext.JobDetail.JobDataMap["PackageFilePath"];
|
|
|
|
PluginLibraryUpdateResponse catalogue;
|
|
string pluginPackagesLocation;
|
|
|
|
if (!Plugins.PluginsLoaded)
|
|
throw new InvalidOperationException("Plugins have not been initialized");
|
|
|
|
List<Tuple<PluginManifest, string, PluginLibraryItem>> updatePlugins;
|
|
|
|
using (DiscoDataContext dbContext = new DiscoDataContext())
|
|
{
|
|
catalogue = Plugins.LoadCatalogue(dbContext);
|
|
pluginPackagesLocation = dbContext.DiscoConfiguration.PluginPackagesLocation;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(pluginId))
|
|
{
|
|
if (string.IsNullOrEmpty(packageFilePath))
|
|
{
|
|
// Update Single from Catalogue
|
|
PluginManifest existingManifest = Plugins.GetPlugin(pluginId);
|
|
var catalogueItem = catalogue.Plugins.FirstOrDefault(p => p.Id == existingManifest.Id);
|
|
|
|
if (catalogueItem == null)
|
|
throw new InvalidOperationException("No updates are available for this Plugin");
|
|
if (Version.Parse(catalogueItem.LatestVersion) <= existingManifest.Version)
|
|
throw new InvalidOperationException("Only newer versions can be used to update a plugin");
|
|
|
|
updatePlugins = new List<Tuple<PluginManifest, string, PluginLibraryItem>>() {
|
|
new Tuple<PluginManifest,string,PluginLibraryItem>(existingManifest, null, catalogueItem)
|
|
};
|
|
}
|
|
else
|
|
{
|
|
// Update Single from Local
|
|
PluginManifest existingManifest = Plugins.GetPlugin(pluginId);
|
|
updatePlugins = new List<Tuple<PluginManifest, string, PluginLibraryItem>>() {
|
|
new Tuple<PluginManifest,string,PluginLibraryItem>(existingManifest, packageFilePath, null)
|
|
};
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Update All
|
|
updatePlugins = Plugins.GetPlugins().Join((IEnumerable<PluginLibraryItem>)catalogue.Plugins, manifest => manifest.Id, update => update.Id, (manifest, update) => new Tuple<PluginManifest, string, PluginLibraryItem>(manifest, null, update)).Where(i => Version.Parse(i.Item3.LatestVersion) > i.Item1.Version).ToList();
|
|
}
|
|
|
|
if (updatePlugins == null || updatePlugins.Count == 0)
|
|
{
|
|
this.Status.Finished("No plugins to update...", "/Config/Plugins");
|
|
return;
|
|
}
|
|
|
|
ExecuteTaskInternal(this.Status, pluginPackagesLocation, updatePlugins);
|
|
|
|
this.Status.Finished("Restarting Disco, please wait...", "/Config/Plugins");
|
|
Plugins.RestartApp(1500);
|
|
}
|
|
|
|
internal static void UpdateOffline(ScheduledTaskStatus Status)
|
|
{
|
|
string pluginsLocation;
|
|
string pluginPackagesLocation;
|
|
string pluginsStorageLocation;
|
|
PluginLibraryUpdateResponse pluginCatalogue;
|
|
List<Tuple<PluginManifest, string, PluginLibraryItem>> UpdatePlugins = new List<Tuple<PluginManifest, string, PluginLibraryItem>>();
|
|
|
|
using (DiscoDataContext dbContext = new DiscoDataContext())
|
|
{
|
|
pluginCatalogue = Plugins.LoadCatalogue(dbContext);
|
|
pluginsLocation = dbContext.DiscoConfiguration.PluginsLocation;
|
|
pluginPackagesLocation = dbContext.DiscoConfiguration.PluginPackagesLocation;
|
|
pluginsStorageLocation = dbContext.DiscoConfiguration.PluginStorageLocation;
|
|
}
|
|
|
|
DirectoryInfo pluginDirectoryRoot = new DirectoryInfo(pluginsLocation);
|
|
if (pluginDirectoryRoot.Exists)
|
|
{
|
|
foreach (DirectoryInfo pluginDirectory in pluginDirectoryRoot.EnumerateDirectories())
|
|
{
|
|
string pluginManifestFilename = Path.Combine(pluginDirectory.FullName, "manifest.json");
|
|
if (File.Exists(pluginManifestFilename))
|
|
{
|
|
PluginManifest pluginManifest = null;
|
|
try
|
|
{
|
|
pluginManifest = PluginManifest.FromPluginManifestFile(pluginManifestFilename);
|
|
}
|
|
catch (Exception) { }
|
|
|
|
if (pluginManifest != null)
|
|
{
|
|
// Check for Update
|
|
var catalogueItem = pluginCatalogue.Plugins.FirstOrDefault(i => i.Id == pluginManifest.Id && Version.Parse(i.LatestVersion) > pluginManifest.Version);
|
|
|
|
if (catalogueItem != null)
|
|
{ // Update Available
|
|
UpdatePlugins.Add(new Tuple<PluginManifest, string, PluginLibraryItem>(pluginManifest, null, catalogueItem));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (UpdatePlugins.Count > 0)
|
|
{
|
|
ExecuteTaskInternal(Status, pluginPackagesLocation, UpdatePlugins);
|
|
}
|
|
}
|
|
|
|
internal static void ExecuteTaskInternal(ScheduledTaskStatus Status, string pluginPackagesLocation, List<Tuple<PluginManifest, string, PluginLibraryItem>> UpdatePlugins)
|
|
{
|
|
while (UpdatePlugins.Count > 0)
|
|
{
|
|
var updatePlugin = UpdatePlugins[0];
|
|
var existingManifest = updatePlugin.Item1;
|
|
var packageTempFilePath = updatePlugin.Item2;
|
|
var catalogueItem = updatePlugin.Item3;
|
|
UpdatePlugins.Remove(updatePlugin);
|
|
|
|
var pluginId = existingManifest != null ? existingManifest.Id : catalogueItem.Id;
|
|
var pluginName = existingManifest != null ? existingManifest.Name : catalogueItem.Name;
|
|
|
|
if (string.IsNullOrEmpty(packageTempFilePath))
|
|
{
|
|
// Download Update
|
|
|
|
Status.UpdateStatus(0, string.Format("Downloading Plugin Package: {0}", pluginName), "Connecting...");
|
|
packageTempFilePath = Path.Combine(pluginPackagesLocation, string.Format("{0}.discoPlugin", pluginId));
|
|
|
|
if (File.Exists(packageTempFilePath))
|
|
File.Delete(packageTempFilePath);
|
|
|
|
if (!Directory.Exists(Path.GetDirectoryName(packageTempFilePath)))
|
|
Directory.CreateDirectory(Path.GetDirectoryName(packageTempFilePath));
|
|
|
|
// Need to Download the Package
|
|
WebClient downloader = new WebClient();
|
|
DateTime progressExpires = DateTime.Now;
|
|
downloader.DownloadProgressChanged += (sender, e) =>
|
|
{
|
|
Console.WriteLine(e.ProgressPercentage);
|
|
if (progressExpires <= DateTime.Now)
|
|
{
|
|
Status.UpdateStatus(e.ProgressPercentage, string.Format("{0} of {1} KB downloaded", e.BytesReceived / 1024, e.TotalBytesToReceive / 1024));
|
|
// Throttle Updates for SignalR
|
|
progressExpires = DateTime.Now.AddMilliseconds(250);
|
|
}
|
|
};
|
|
downloader.DownloadFileTaskAsync(new Uri(catalogueItem.LatestDownloadUrl), packageTempFilePath).Wait();
|
|
}
|
|
|
|
Status.UpdateStatus(10, "Opening Plugin Package", Path.GetFileName(packageTempFilePath));
|
|
|
|
PluginManifest updateManifest;
|
|
|
|
using (var packageStream = File.OpenRead(packageTempFilePath))
|
|
{
|
|
using (ZipArchive packageArchive = new ZipArchive(packageStream, ZipArchiveMode.Read, false))
|
|
{
|
|
|
|
ZipArchiveEntry packageManifestEntry = packageArchive.GetEntry("manifest.json");
|
|
if (packageManifestEntry == null)
|
|
throw new InvalidDataException("The plugin package does not contain the 'manifest.json' entry");
|
|
|
|
using (Stream packageManifestStream = packageManifestEntry.Open())
|
|
{
|
|
updateManifest = PluginManifest.FromPluginManifestFile(packageManifestStream);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure not already installed
|
|
if (existingManifest != null)
|
|
if (updateManifest.Version <= existingManifest.Version)
|
|
throw new InvalidOperationException("Only newer versions can be used to update a plugin");
|
|
|
|
Status.UpdateStatus(20, string.Format("{0} [{1} v{2}] by {3}", updateManifest.Name, updateManifest.Id, updateManifest.Version.ToString(4), updateManifest.Author), "Initializing Update Environment");
|
|
|
|
using (DiscoDataContext dbContext = new DiscoDataContext())
|
|
{
|
|
// Check for Compatibility
|
|
var compatibilityData = Plugins.LoadCompatibilityData(dbContext);
|
|
var pluginCompatibility = compatibilityData.Plugins.FirstOrDefault(i => i.Id.Equals(updateManifest.Id, StringComparison.InvariantCultureIgnoreCase) && updateManifest.Version == Version.Parse(i.Version));
|
|
if (pluginCompatibility != null && !pluginCompatibility.Compatible)
|
|
throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] is not compatible: {2}", updateManifest.Id, updateManifest.VersionFormatted, pluginCompatibility.Reason));
|
|
|
|
var updatePluginPath = Path.Combine(dbContext.DiscoConfiguration.PluginsLocation, string.Format("{0}.discoPlugin", updateManifest.Id));
|
|
File.Move(packageTempFilePath, updatePluginPath);
|
|
|
|
if (existingManifest != null)
|
|
{
|
|
PluginsLog.LogBeforeUpdate(existingManifest, updateManifest);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static ScheduledTaskStatus UpdateHelper(string PluginId = null, string PackageFilePath = null)
|
|
{
|
|
if (ScheduledTasks.GetTaskStatuses(typeof(UpdatePluginTask)).Where(s => s.IsRunning).Count() > 0)
|
|
throw new InvalidOperationException("A plugin is already being Updated");
|
|
if (ScheduledTasks.GetTaskStatuses(typeof(UninstallPluginTask)).Where(s => s.IsRunning).Count() > 0)
|
|
throw new InvalidOperationException("A plugin is being Uninstalled");
|
|
if (ScheduledTasks.GetTaskStatuses(typeof(InstallPluginTask)).Where(s => s.IsRunning).Count() > 0)
|
|
throw new InvalidOperationException("A plugin is being Installed");
|
|
|
|
JobDataMap taskData = new JobDataMap() { { "PluginId", PluginId }, { "PackageFilePath", PackageFilePath } };
|
|
|
|
var instance = new UpdatePluginTask();
|
|
|
|
return instance.ScheduleTask(taskData);
|
|
}
|
|
|
|
public static ScheduledTaskStatus UpdateLocalPlugin(string PluginId, string PackageFilePath)
|
|
{
|
|
return UpdateHelper(PluginId, PackageFilePath);
|
|
}
|
|
public static ScheduledTaskStatus UpdatePlugin(string PluginId)
|
|
{
|
|
return UpdateHelper(PluginId);
|
|
}
|
|
public static ScheduledTaskStatus UpdateAllPlugins()
|
|
{
|
|
return UpdateHelper();
|
|
}
|
|
}
|
|
}
|