Update: Plugin Framework Install & UI
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Disco.Data.Repository;
|
||||
@@ -17,12 +18,38 @@ namespace Disco.Services.Plugins
|
||||
|
||||
protected override void ExecuteTask()
|
||||
{
|
||||
string packageUrlPath = (string)this.ExecutionContext.JobDetail.JobDataMap["PackageUrl"];
|
||||
string packageFilePath = (string)this.ExecutionContext.JobDetail.JobDataMap["PackageFilePath"];
|
||||
bool DeletePackageAfterInstall = (bool)this.ExecutionContext.JobDetail.JobDataMap["DeletePackageAfterInstall"];
|
||||
|
||||
if (!Plugins.PluginsLoaded)
|
||||
throw new InvalidOperationException("Plugins have not been initialized");
|
||||
|
||||
if (!string.IsNullOrEmpty(packageUrlPath))
|
||||
{
|
||||
this.Status.UpdateStatus(0, "Downloading Plugin Package", "Connecting...");
|
||||
|
||||
if (File.Exists(packageFilePath))
|
||||
File.Delete(packageFilePath);
|
||||
|
||||
if (!Directory.Exists(Path.GetDirectoryName(packageFilePath)))
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(packageFilePath));
|
||||
|
||||
// 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)
|
||||
{
|
||||
this.Status.UpdateStatus(e.ProgressPercentage, string.Format("{0} of {1} KB downloaded", e.BytesReceived / 1024, e.TotalBytesToReceive / 1024));
|
||||
progressExpires = DateTime.Now.AddMilliseconds(250);
|
||||
}
|
||||
};
|
||||
downloader.DownloadFileTaskAsync(new Uri(packageUrlPath), packageFilePath).Wait();
|
||||
}
|
||||
|
||||
this.Status.UpdateStatus(10, "Opening Plugin Package", Path.GetFileName(packageFilePath));
|
||||
|
||||
using (var packageStream = File.OpenRead(packageFilePath))
|
||||
@@ -44,7 +71,7 @@ namespace Disco.Services.Plugins
|
||||
this.Status.UpdateStatus(20, string.Format("{0} [{1} v{2}] by {3}", packageManifest.Name, packageManifest.Id, packageManifest.Version.ToString(4), packageManifest.Author), "Initializing Install Environment");
|
||||
|
||||
PluginsLog.LogInstalling(packageManifest);
|
||||
|
||||
|
||||
lock (Plugins._PluginLock)
|
||||
{
|
||||
if (!Plugins.PluginsLoaded)
|
||||
@@ -58,6 +85,12 @@ namespace Disco.Services.Plugins
|
||||
{
|
||||
string packagePath = Path.Combine(dbContext.DiscoConfiguration.PluginsLocation, packageManifest.Id);
|
||||
|
||||
// Check for Compatibility
|
||||
var compatibilityData = Plugins.LoadCompatibilityData(dbContext);
|
||||
var pluginCompatibility = compatibilityData.Plugins.FirstOrDefault(i => i.Id.Equals(packageManifest.Id, StringComparison.InvariantCultureIgnoreCase) && packageManifest.Version == Version.Parse(i.Version));
|
||||
if (pluginCompatibility != null && !pluginCompatibility.Compatible)
|
||||
throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] is not compatible: {2}", packageManifest.Id, packageManifest.VersionFormatted, pluginCompatibility.Reason));
|
||||
|
||||
// Force Delete of Existing Folder
|
||||
if (Directory.Exists(packagePath))
|
||||
{
|
||||
@@ -117,12 +150,16 @@ namespace Disco.Services.Plugins
|
||||
File.Delete(packageFilePath);
|
||||
}
|
||||
|
||||
public static ScheduledTaskStatus InstallPlugin(string PackageFilePath, bool DeletePackageAfterInstall)
|
||||
public static ScheduledTaskStatus InstallLocalPlugin(string PackageFilePath, bool DeletePackageAfterInstall)
|
||||
{
|
||||
return InstallPlugin(null, PackageFilePath, DeletePackageAfterInstall);
|
||||
}
|
||||
public static ScheduledTaskStatus InstallPlugin(string PackageUrl, string PackageFilePath, bool DeletePackageAfterInstall)
|
||||
{
|
||||
if (ScheduledTasks.GetTaskStatuses(typeof(InstallPluginTask)).Where(s => s.IsRunning).Count() > 0)
|
||||
throw new InvalidOperationException("A plugin is already being Installed");
|
||||
|
||||
JobDataMap taskData = new JobDataMap() { { "PackageFilePath", PackageFilePath }, { "DeletePackageAfterInstall", DeletePackageAfterInstall } };
|
||||
JobDataMap taskData = new JobDataMap() { { "PackageUrl", PackageUrl }, { "PackageFilePath", PackageFilePath }, { "DeletePackageAfterInstall", DeletePackageAfterInstall } };
|
||||
|
||||
var instance = new InstallPluginTask();
|
||||
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class PluginAttribute : Attribute
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Author { get; set; }
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class PluginAttribute : Attribute
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Author { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string HostVersionMin { get; set; }
|
||||
public string HostVersionMax { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,27 @@ using System.Web.Mvc;
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Services.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
public class PluginManifest
|
||||
{
|
||||
[JsonProperty]
|
||||
public string Id { get; set; }
|
||||
[JsonProperty]
|
||||
public string Name { get; set; }
|
||||
[JsonProperty]
|
||||
public string Author { get; set; }
|
||||
[JsonProperty]
|
||||
public string Url { get; set; }
|
||||
[JsonProperty]
|
||||
public Version Version { get; set; }
|
||||
[JsonProperty]
|
||||
public Version HostVersionMin { get; set; }
|
||||
[JsonProperty]
|
||||
public Version HostVersionMax { get; set; }
|
||||
[JsonProperty]
|
||||
internal string AssemblyPath { get; set; }
|
||||
[JsonProperty]
|
||||
private string TypeName { get; set; }
|
||||
@@ -49,6 +60,16 @@ namespace Disco.Services.Plugins
|
||||
[JsonIgnore]
|
||||
public string StorageLocation { get; private set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string VersionFormatted
|
||||
{
|
||||
get
|
||||
{
|
||||
var v = Version;
|
||||
return string.Format("{0}.{1}.{2:0000}.{3:0000}", v.Major, v.Minor, v.Build, v.Revision);
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
private bool environmentInitalized { get; set; }
|
||||
|
||||
@@ -83,7 +104,7 @@ namespace Disco.Services.Plugins
|
||||
return manifest;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes a Json Manifest
|
||||
/// </summary>
|
||||
@@ -98,7 +119,7 @@ namespace Disco.Services.Plugins
|
||||
manifestString = manifestStreamReader.ReadToEnd();
|
||||
}
|
||||
|
||||
var manifest = JsonConvert.DeserializeObject<PluginManifest>(manifestString);
|
||||
var manifest = JsonConvert.DeserializeObject<PluginManifest>(manifestString, new VersionConverter());
|
||||
|
||||
manifest.PluginLocation = PluginLocation;
|
||||
|
||||
@@ -129,6 +150,10 @@ namespace Disco.Services.Plugins
|
||||
var pluginId = pluginAttributes.Id;
|
||||
var pluginName = pluginAttributes.Name;
|
||||
var pluginAuthor = pluginAttributes.Author;
|
||||
var pluginUrl = pluginAttributes.Url;
|
||||
|
||||
var pluginHostVersionMin = pluginAttributes.HostVersionMin == null ? null : Version.Parse(pluginAttributes.HostVersionMin);
|
||||
var pluginHostVersionMax = pluginAttributes.HostVersionMax == null ? null : Version.Parse(pluginAttributes.HostVersionMax);
|
||||
|
||||
var pluginVersion = assemblyName.Version;
|
||||
var pluginAssemblyPath = Path.GetFileName(assembly.Location);
|
||||
@@ -167,6 +192,9 @@ namespace Disco.Services.Plugins
|
||||
Name = pluginName,
|
||||
Author = pluginAuthor,
|
||||
Version = pluginVersion,
|
||||
Url = pluginUrl,
|
||||
HostVersionMin = pluginHostVersionMin,
|
||||
HostVersionMax = pluginHostVersionMax,
|
||||
AssemblyPath = pluginAssemblyPath,
|
||||
TypeName = pluginTypeName,
|
||||
AssemblyReferences = pluginAssemblyReferences,
|
||||
@@ -188,7 +216,7 @@ namespace Disco.Services.Plugins
|
||||
|
||||
public string ToManifestFile()
|
||||
{
|
||||
return JsonConvert.SerializeObject(this, Formatting.Indented);
|
||||
return JsonConvert.SerializeObject(this, Formatting.Indented, new VersionConverter());
|
||||
}
|
||||
private bool InitializePluginEnvironment(DiscoDataContext dbContext)
|
||||
{
|
||||
@@ -362,7 +390,7 @@ namespace Disco.Services.Plugins
|
||||
var fileDateCheck = System.IO.File.GetLastWriteTime(resourcePath);
|
||||
if (fileDateCheck == resourceHash.Item2)
|
||||
#endif
|
||||
return new Tuple<string, string>(resourcePath, resourceHash.Item1);
|
||||
return new Tuple<string, string>(resourcePath, resourceHash.Item1);
|
||||
}
|
||||
|
||||
if (!File.Exists(resourcePath))
|
||||
|
||||
@@ -7,6 +7,9 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Disco.Data.Repository;
|
||||
using System.IO.Compression;
|
||||
using Disco.Models.BI.Interop.Community;
|
||||
using System.Web;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
@@ -42,6 +45,15 @@ namespace Disco.Services.Plugins
|
||||
}
|
||||
}
|
||||
|
||||
public static bool PluginInstalled(string PluginId)
|
||||
{
|
||||
if (_PluginManifests == null)
|
||||
throw new InvalidOperationException("Plugins have not been initialized");
|
||||
|
||||
PluginManifest manifest;
|
||||
return _PluginManifests.TryGetValue(PluginId, out manifest);
|
||||
}
|
||||
|
||||
public static PluginManifest GetPlugin(string PluginId, Type ContainsCategoryType)
|
||||
{
|
||||
if (_PluginManifests == null)
|
||||
@@ -145,6 +157,81 @@ namespace Disco.Services.Plugins
|
||||
throw new InvalidOperationException(string.Format("Unknown Plugin Feature Category Type: [{0}]", FeatureCategoryType.Name));
|
||||
}
|
||||
|
||||
public static string CatalogueFile(DiscoDataContext dbContext)
|
||||
{
|
||||
return Path.Combine(dbContext.DiscoConfiguration.PluginPackagesLocation, "Catalogue.json");
|
||||
}
|
||||
public static string CompatibilityFile(DiscoDataContext dbContext)
|
||||
{
|
||||
return Path.Combine(dbContext.DiscoConfiguration.PluginPackagesLocation, "Compatibility.json");
|
||||
}
|
||||
|
||||
public static PluginLibraryUpdateResponse LoadCatalogue(DiscoDataContext dbContext)
|
||||
{
|
||||
var catalogueFile = CatalogueFile(dbContext);
|
||||
|
||||
if (!File.Exists(catalogueFile))
|
||||
return null;
|
||||
|
||||
return JsonConvert.DeserializeObject<PluginLibraryUpdateResponse>(File.ReadAllText(catalogueFile));
|
||||
}
|
||||
|
||||
public static PluginLibraryCompatibilityResponse LoadCompatibilityData(DiscoDataContext dbContext)
|
||||
{
|
||||
var pluginAssembly = typeof(Plugins).Assembly;
|
||||
Version hostVersion = pluginAssembly.GetName().Version;
|
||||
PluginLibraryCompatibilityResponse Data = null;
|
||||
var localCompatFile = Path.Combine(Path.GetDirectoryName(pluginAssembly.Location), "ReleasePluginCompatibility.json");
|
||||
var serverCompatFile = CompatibilityFile(dbContext);
|
||||
|
||||
if (File.Exists(localCompatFile))
|
||||
{
|
||||
Data = JsonConvert.DeserializeObject<PluginLibraryCompatibilityResponse>(File.ReadAllText(localCompatFile));
|
||||
Data.HostVersion = hostVersion.ToString(4);
|
||||
}
|
||||
if (File.Exists(serverCompatFile))
|
||||
{
|
||||
var serverData = JsonConvert.DeserializeObject<PluginLibraryCompatibilityResponse>(File.ReadAllText(serverCompatFile));
|
||||
if (Version.Parse(serverData.HostVersion) == hostVersion)
|
||||
{
|
||||
if (Data == null)
|
||||
{
|
||||
// No Local Compatibility File
|
||||
Data = serverData;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Join Compatibility Files
|
||||
var localItems = Data.Plugins;
|
||||
var localItemVersions = localItems.ToDictionary(i => i, i => Version.Parse(i.Version));
|
||||
var joinedItems = localItems.ToList();
|
||||
Data.ResponseTimestamp = serverData.ResponseTimestamp;
|
||||
foreach (var serverItem in serverData.Plugins)
|
||||
{
|
||||
var serverItemVersion = Version.Parse(serverItem.Version);
|
||||
var localItem = localItems.FirstOrDefault(i => i.Id.Equals(serverItem.Id, StringComparison.InvariantCultureIgnoreCase) && serverItemVersion == localItemVersions[i]);
|
||||
if (localItem != null)
|
||||
joinedItems.Remove(localItem);
|
||||
|
||||
joinedItems.Add(serverItem);
|
||||
}
|
||||
Data.Plugins = joinedItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Data == null)
|
||||
{
|
||||
Data = new PluginLibraryCompatibilityResponse()
|
||||
{
|
||||
HostVersion = hostVersion.ToString(4),
|
||||
Plugins = new List<PluginLibraryCompatibilityItem>(),
|
||||
ResponseTimestamp = new DateTime(2011, 7, 1)
|
||||
};
|
||||
}
|
||||
|
||||
return Data;
|
||||
}
|
||||
|
||||
public static void InitalizePlugins(DiscoDataContext dbContext)
|
||||
{
|
||||
if (_PluginManifests == null)
|
||||
@@ -153,6 +240,8 @@ namespace Disco.Services.Plugins
|
||||
{
|
||||
if (_PluginManifests == null)
|
||||
{
|
||||
Version hostVersion = typeof(Plugins).Assembly.GetName().Version;
|
||||
var compatibilityData = new Lazy<PluginLibraryCompatibilityResponse>(() => LoadCompatibilityData(dbContext));
|
||||
Dictionary<string, PluginManifest> loadedPlugins = new Dictionary<string, PluginManifest>();
|
||||
|
||||
PluginPath = dbContext.DiscoConfiguration.PluginsLocation;
|
||||
@@ -185,11 +274,21 @@ namespace Disco.Services.Plugins
|
||||
if (File.Exists(updatePackagePath))
|
||||
{
|
||||
// Update Plugin
|
||||
pluginManifest = UpdatePlugin(dbContext, pluginManifest, updatePackagePath);
|
||||
pluginManifest = UpdatePlugin(dbContext, pluginManifest, updatePackagePath, compatibilityData.Value);
|
||||
}
|
||||
|
||||
if (pluginManifest != null)
|
||||
{
|
||||
// Check Version Compatibility
|
||||
var pluginCompatibility = compatibilityData.Value.Plugins.FirstOrDefault(i => i.Id.Equals(pluginManifest.Id, StringComparison.InvariantCultureIgnoreCase) && pluginManifest.Version == Version.Parse(i.Version));
|
||||
if (pluginCompatibility != null && !pluginCompatibility.Compatible)
|
||||
throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] is not compatible: {2}", pluginManifest.Id, pluginManifest.VersionFormatted, pluginCompatibility.Reason));
|
||||
|
||||
if (pluginManifest.HostVersionMin != null && pluginManifest.HostVersionMin > hostVersion)
|
||||
throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] does not support this version of Disco (Requires v{2} or greater)", pluginManifest.Id, pluginManifest.VersionFormatted, pluginManifest.HostVersionMin.ToString()));
|
||||
if (pluginManifest.HostVersionMax != null && pluginManifest.HostVersionMax < hostVersion)
|
||||
throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] does not support this version of Disco (Support expired as of v{2})", pluginManifest.Id, pluginManifest.VersionFormatted, pluginManifest.HostVersionMax.ToString()));
|
||||
|
||||
pluginManifest.InitializePlugin(dbContext);
|
||||
loadedPlugins[pluginManifest.Id] = pluginManifest;
|
||||
}
|
||||
@@ -251,13 +350,13 @@ namespace Disco.Services.Plugins
|
||||
_PluginAssemblyManifests = _PluginManifests.Values.ToDictionary(p => p.PluginAssembly, p => p);
|
||||
}
|
||||
|
||||
public static PluginManifest UpdatePlugin(DiscoDataContext dbContext, PluginManifest ExistingManifest, String UpdatePluginPackageFilePath)
|
||||
public static PluginManifest UpdatePlugin(DiscoDataContext dbContext, PluginManifest ExistingManifest, String UpdatePluginPackageFilePath, PluginLibraryCompatibilityResponse CompatibilityData = null)
|
||||
{
|
||||
PluginManifest updatedManifest;
|
||||
|
||||
using (var packageStream = File.OpenRead(UpdatePluginPackageFilePath))
|
||||
{
|
||||
updatedManifest = UpdatePlugin(dbContext, ExistingManifest, packageStream);
|
||||
updatedManifest = UpdatePlugin(dbContext, ExistingManifest, packageStream, CompatibilityData);
|
||||
}
|
||||
|
||||
// Remove Update after processing
|
||||
@@ -266,7 +365,7 @@ namespace Disco.Services.Plugins
|
||||
return updatedManifest;
|
||||
}
|
||||
|
||||
public static PluginManifest UpdatePlugin(DiscoDataContext dbContext, PluginManifest ExistingManifest, Stream UpdatePluginPackage)
|
||||
public static PluginManifest UpdatePlugin(DiscoDataContext dbContext, PluginManifest ExistingManifest, Stream UpdatePluginPackage, PluginLibraryCompatibilityResponse CompatibilityData = null)
|
||||
{
|
||||
using (MemoryStream packageStream = new MemoryStream())
|
||||
{
|
||||
@@ -301,6 +400,13 @@ namespace Disco.Services.Plugins
|
||||
throw new InvalidDataException("A newer version of this plugin is already installed");
|
||||
}
|
||||
|
||||
// Check Compatibility
|
||||
if (CompatibilityData == null)
|
||||
CompatibilityData = LoadCompatibilityData(dbContext);
|
||||
var pluginCompatibility = CompatibilityData.Plugins.FirstOrDefault(i => i.Id.Equals(packageManifest.Id, StringComparison.InvariantCultureIgnoreCase) && packageManifest.Version == Version.Parse(i.Version));
|
||||
if (pluginCompatibility != null && !pluginCompatibility.Compatible)
|
||||
throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] is not compatible: {2}", packageManifest.Id, packageManifest.VersionFormatted, pluginCompatibility.Reason));
|
||||
|
||||
string packagePath = Path.Combine(dbContext.DiscoConfiguration.PluginsLocation, packageManifest.Id);
|
||||
|
||||
// Force Delete of Existing Folder
|
||||
|
||||
Reference in New Issue
Block a user