diff --git a/Disco.Models/BI/Interop/Community/PluginLibraryCompatibilityItem.cs b/Disco.Models/BI/Interop/Community/PluginLibraryCompatibilityItem.cs deleted file mode 100644 index 74ff740b..00000000 --- a/Disco.Models/BI/Interop/Community/PluginLibraryCompatibilityItem.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Disco.Models.BI.Interop.Community -{ - public class PluginLibraryCompatibilityItem - { - public string Id { get; set; } - public string Version { get; set; } - public bool Compatible { get; set; } - public string Reason { get; set; } - } -} diff --git a/Disco.Models/BI/Interop/Community/PluginLibraryCompatibilityRequest.cs b/Disco.Models/BI/Interop/Community/PluginLibraryCompatibilityRequest.cs deleted file mode 100644 index 14fd0eaa..00000000 --- a/Disco.Models/BI/Interop/Community/PluginLibraryCompatibilityRequest.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Disco.Models.BI.Interop.Community -{ - public class PluginLibraryCompatibilityRequest - { - public string HostVersion { get; set; } - public string DeploymentId { get; set; } - } -} diff --git a/Disco.Models/BI/Interop/Community/PluginLibraryCompatibilityResponse.cs b/Disco.Models/BI/Interop/Community/PluginLibraryCompatibilityResponse.cs deleted file mode 100644 index 9af50f48..00000000 --- a/Disco.Models/BI/Interop/Community/PluginLibraryCompatibilityResponse.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Disco.Models.BI.Interop.Community -{ - public class PluginLibraryCompatibilityResponse - { - public string HostVersion { get; set; } - public DateTime ResponseTimestamp { get; set; } - public List Plugins { get; set; } - } -} diff --git a/Disco.Models/BI/Interop/Community/PluginLibraryItem.cs b/Disco.Models/BI/Interop/Community/PluginLibraryItem.cs deleted file mode 100644 index a8b7d117..00000000 --- a/Disco.Models/BI/Interop/Community/PluginLibraryItem.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Disco.Models.BI.Interop.Community -{ - public class PluginLibraryItem - { - public string Id { get; set; } - public string Name { get; set; } - public string Author { get; set; } - public string Url { get; set; } - public string Blurb { get; set; } - - public string LatestVersion { get; set; } - public string LatestChangeLog { get; set; } - public string LatestHostVersionMin { get; set; } - public string LatestHostVersionMax { get; set; } - public string LatestDownloadUrl { get; set; } - } -} diff --git a/Disco.Models/BI/Interop/Community/PluginLibraryUpdateRequest.cs b/Disco.Models/BI/Interop/Community/PluginLibraryUpdateRequest.cs deleted file mode 100644 index 11199452..00000000 --- a/Disco.Models/BI/Interop/Community/PluginLibraryUpdateRequest.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Disco.Models.BI.Interop.Community -{ - public class PluginLibraryUpdateRequest - { - public string HostVersion { get; set; } - public string DeploymentId { get; set; } - } -} diff --git a/Disco.Models/BI/Interop/Community/PluginLibraryUpdateResponse.cs b/Disco.Models/BI/Interop/Community/PluginLibraryUpdateResponse.cs deleted file mode 100644 index 9905e483..00000000 --- a/Disco.Models/BI/Interop/Community/PluginLibraryUpdateResponse.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Disco.Models.BI.Interop.Community -{ - public class PluginLibraryUpdateResponse - { - public DateTime ResponseTimestamp { get; set; } - public List Plugins { get; set; } - } -} diff --git a/Disco.Models/Disco.Models.csproj b/Disco.Models/Disco.Models.csproj index 9382c795..6ece3428 100644 --- a/Disco.Models/Disco.Models.csproj +++ b/Disco.Models/Disco.Models.csproj @@ -57,12 +57,6 @@ - - - - - - @@ -109,6 +103,9 @@ + + + @@ -182,7 +179,7 @@ - + diff --git a/Disco.Models/Services/Interop/DiscoServices/PluginIncompatibility.cs b/Disco.Models/Services/Interop/DiscoServices/PluginIncompatibility.cs new file mode 100644 index 00000000..22615c68 --- /dev/null +++ b/Disco.Models/Services/Interop/DiscoServices/PluginIncompatibility.cs @@ -0,0 +1,12 @@ +using System; + +namespace Disco.Models.Services.Interop.DiscoServices +{ + public class PluginIncompatibility + { + public string PluginId { get; set; } + public Version Version { get; set; } + + public string Reason { get; set; } + } +} diff --git a/Disco.Models/Services/Interop/DiscoServices/PluginLibraryIncompatibility.cs b/Disco.Models/Services/Interop/DiscoServices/PluginLibraryIncompatibility.cs new file mode 100644 index 00000000..b43912d6 --- /dev/null +++ b/Disco.Models/Services/Interop/DiscoServices/PluginLibraryIncompatibility.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Disco.Models.Services.Interop.DiscoServices +{ + public class PluginLibraryIncompatibility + { + + public List IncompatiblePlugins { get; set; } + + } +} diff --git a/Disco.Models/Services/Interop/DiscoServices/PluginLibraryManifestV2.cs b/Disco.Models/Services/Interop/DiscoServices/PluginLibraryManifestV2.cs new file mode 100644 index 00000000..66554807 --- /dev/null +++ b/Disco.Models/Services/Interop/DiscoServices/PluginLibraryManifestV2.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; + +namespace Disco.Models.Services.Interop.DiscoServices +{ + public class PluginLibraryManifestV2 + { + public DateTime ManifestDate { get; set; } + + public List Plugins { get; set; } + } + + public class PluginLibraryItemV2 + { + public string Id { get; set; } + public string Name { get; set; } + public string Author { get; set; } + public string InformationUrl { get; set; } + + public string PrimaryFeatureCategory { get; set; } + + public string Description { get; set; } + + public List Releases { get; set; } + } + + public class PluginLibraryItemReleaseV2 + { + public string PluginId { get; set; } + public string Version { get; set; } + + public string HostMinVersion { get; set; } + public string HostMaxVersion { get; set; } + + public bool Blocked { get; set; } + + public string Description { get; set; } + + public string DownloadUrl { get; set; } + } +} diff --git a/Disco.Services/Disco.Services.csproj b/Disco.Services/Disco.Services.csproj index d2b3af3e..898c57c4 100644 --- a/Disco.Services/Disco.Services.csproj +++ b/Disco.Services/Disco.Services.csproj @@ -248,6 +248,8 @@ + + @@ -275,7 +277,6 @@ - @@ -379,7 +380,7 @@ - + diff --git a/Disco.Services/Interop/DiscoServices/PluginLibrary.cs b/Disco.Services/Interop/DiscoServices/PluginLibrary.cs new file mode 100644 index 00000000..3b3c269c --- /dev/null +++ b/Disco.Services/Interop/DiscoServices/PluginLibrary.cs @@ -0,0 +1,149 @@ +using Disco.Data.Repository; +using Disco.Models.Services.Interop.DiscoServices; +using Disco.Services.Tasks; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Web; + +namespace Disco.Services.Interop.DiscoServices +{ + public static class PluginLibrary + { + private static string UpdateUrl() + { + return string.Concat(DiscoServiceHelpers.ServicesUrl, "API/Plugins/Library/V2"); + } + + public static string InitialManifestFilename() + { + return HttpContext.Current.Server.MapPath("~/ClientBin/DiscoServices.InitialPluginLibraryManifest.json"); + } + + public static string ManifestFilename(DiscoDataContext Database) + { + return Path.Combine(Database.DiscoConfiguration.PluginPackagesLocation, "LibraryManifest.json"); + } + + public static PluginLibraryManifestV2 LoadManifest(DiscoDataContext Database) + { + var manifestFile = ManifestFilename(Database); + + if (File.Exists(manifestFile)) + { + return JsonConvert.DeserializeObject(File.ReadAllText(manifestFile)); + } + else + { + // Use Initial Plugin Library Manifest + manifestFile = InitialManifestFilename(); + + if (File.Exists(manifestFile)) + return JsonConvert.DeserializeObject(File.ReadAllText(manifestFile)); + + throw new FileNotFoundException("No plugin library manifest file was found", manifestFile); + } + } + + public static PluginLibraryIncompatibility LoadIncompatibilityData(this PluginLibraryManifestV2 LibraryManifest) + { + var pluginAssembly = typeof(PluginLibrary).Assembly; + Version hostVersion = pluginAssembly.GetName().Version; + + return new PluginLibraryIncompatibility() + { + IncompatiblePlugins = LibraryManifest.Plugins.SelectMany(p => p.Releases, (p, r) => + { + var rVersion = Version.Parse(r.Version); + + if (r.Blocked) + return new PluginIncompatibility() { PluginId = r.PluginId, Version = rVersion, Reason = "This plugin release is blocked by Disco ICT Online Services" }; + + if (r.HostMinVersion != null && hostVersion < Version.Parse(r.HostMinVersion)) + return new PluginIncompatibility() { PluginId = r.PluginId, Version = rVersion, Reason = string.Format("This plugin requires v{0} or newer", r.HostMinVersion) }; + + if (r.HostMaxVersion != null && hostVersion > Version.Parse(r.HostMaxVersion)) + return new PluginIncompatibility() { PluginId = r.PluginId, Version = rVersion, Reason = string.Format("This plugin requires v{0} or older", r.HostMaxVersion) }; + + return null; + }).Where(i => i != null).ToList() + }; + } + + public static PluginLibraryManifestV2 UpdateManifest(DiscoDataContext Database, IScheduledTaskStatus Status) + { + Status.UpdateStatus(10, "Sending Request"); + + PluginLibraryManifestV2 result; + + var discoVersion = UpdateQuery.CurrentDiscoVersionFormatted(); + var url = UpdateUrl(); + + using (var httpClient = new HttpClient()) + { + using (var formData = new FormUrlEncodedContent(new KeyValuePair[] { + new KeyValuePair("DeploymentId", Database.DiscoConfiguration.DeploymentId), + new KeyValuePair("DiscoVersion", discoVersion) + })) + { + var response = httpClient.PostAsync(url, formData).Result; + + response.EnsureSuccessStatusCode(); + + Status.UpdateStatus(50, "Waiting for Response"); + + var resultJson = response.Content.ReadAsStringAsync().Result; + + Status.UpdateStatus(90, "Processing Response"); + + result = JsonConvert.DeserializeObject(resultJson); + } + } + + var manifestJson = JsonConvert.SerializeObject(result); + + var manifestFile = PluginLibrary.ManifestFilename(Database); + + if (!Directory.Exists(Path.GetDirectoryName(manifestFile))) + Directory.CreateDirectory(Path.GetDirectoryName(manifestFile)); + + File.WriteAllText(manifestFile, manifestJson); + + return result; + } + + public static PluginLibraryItemReleaseV2 LatestCompatibleRelease(this PluginLibraryItemV2 LibraryItem, PluginLibraryIncompatibility Incompatibility) + { + return LibraryItem.Releases.OrderByDescending(r => Version.Parse(r.Version)).FirstOrDefault(r => Incompatibility.IsCompatible(r)); + } + + public static bool IsCompatible(this PluginLibraryIncompatibility IncompatibilityLibrary, PluginLibraryItemReleaseV2 Release) + { + PluginIncompatibility incompatibility; + + return IsCompatible(IncompatibilityLibrary, Release, out incompatibility); + } + + public static bool IsCompatible(this PluginLibraryIncompatibility IncompatibilityLibrary, PluginLibraryItemReleaseV2 Release, out PluginIncompatibility Incompatibility) + { + return IsCompatible(IncompatibilityLibrary, Release.PluginId, Version.Parse(Release.Version), out Incompatibility); + } + + public static bool IsCompatible(this PluginLibraryIncompatibility IncompatibilityLibrary, string PluginId, Version Version) + { + PluginIncompatibility incompatibility; + + return IsCompatible(IncompatibilityLibrary, PluginId, Version, out incompatibility); + } + + public static bool IsCompatible(this PluginLibraryIncompatibility IncompatibilityLibrary, string PluginId, Version Version, out PluginIncompatibility Incompatibility) + { + Incompatibility = IncompatibilityLibrary.IncompatiblePlugins.FirstOrDefault(i => i.PluginId.Equals(PluginId, StringComparison.OrdinalIgnoreCase) && i.Version == Version); + + return Incompatibility == null; + } + } +} diff --git a/Disco.Services/Interop/DiscoServices/PluginLibraryUpdateTask.cs b/Disco.Services/Interop/DiscoServices/PluginLibraryUpdateTask.cs new file mode 100644 index 00000000..8178f05b --- /dev/null +++ b/Disco.Services/Interop/DiscoServices/PluginLibraryUpdateTask.cs @@ -0,0 +1,63 @@ +using Disco.Data.Repository; +using Disco.Services.Tasks; +using Newtonsoft.Json; +using Quartz; +using System; +using System.IO; +using System.Linq; + +namespace Disco.Services.Interop.DiscoServices +{ + public class PluginLibraryUpdateTask : ScheduledTask + { + public override string TaskName { get { return "Disco ICT - Update Plugin Library"; } } + public override bool SingleInstanceTask { get { return true; } } + public override bool CancelInitiallySupported { get { return false; } } + + public override void InitalizeScheduledTask(DiscoDataContext Database) + { + // Random time between midday and midnight. + var rnd = new Random(); + + var rndHour = rnd.Next(12, 23); + var rndMinute = rnd.Next(0, 59); + + TriggerBuilder triggerBuilder = TriggerBuilder.Create(). + WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(rndHour, rndMinute)); + + this.ScheduleTask(triggerBuilder); + } + + protected override void ExecuteTask() + { + using (DiscoDataContext database = new DiscoDataContext()) + { + Status.UpdateStatus(1, "Updating Plugin Library Manifest", "Initializing"); + + var manifest = PluginLibrary.UpdateManifest(database, this.Status); + + Status.SetFinishedMessage("The Plugin Library Manifest was updated successfully"); + } + } + + public static ScheduledTaskStatus ScheduleNow() + { + var taskStatus = RunningStatus; + if (taskStatus != null) + return taskStatus; + else + { + var task = new PluginLibraryUpdateTask(); + return task.ScheduleTask(); + } + } + + public static ScheduledTaskStatus RunningStatus + { + get + { + return ScheduledTasks.GetTaskStatuses(typeof(PluginLibraryUpdateTask)).Where(ts => ts.IsRunning).FirstOrDefault(); + } + } + } +} diff --git a/Disco.Services/Plugins/CommunityInterop/PluginLibraryUpdateTask.cs b/Disco.Services/Plugins/CommunityInterop/PluginLibraryUpdateTask.cs deleted file mode 100644 index a86d0558..00000000 --- a/Disco.Services/Plugins/CommunityInterop/PluginLibraryUpdateTask.cs +++ /dev/null @@ -1,218 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Disco.Data.Repository; -using Disco.Services.Plugins; -using Disco.Services.Tasks; -using Disco.Models.BI.Interop.Community; -using System.Net; -using System.Xml.Serialization; -using System.IO; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Quartz; -using Disco.Services.Interop.DiscoServices; - -namespace Disco.Services.Plugins.CommunityInterop -{ - public class PluginLibraryUpdateTask : ScheduledTask - { - public override string TaskName { get { return "Disco Community - Update Plugin Library"; } } - - public static string CurrentDiscoVersion() - { - var AssemblyVersion = typeof(PluginLibraryUpdateTask).Assembly.GetName().Version; - return string.Format("{0}.{1}.{2:0000}.{3:0000}", AssemblyVersion.Major, AssemblyVersion.Minor, AssemblyVersion.Build, AssemblyVersion.Revision); - } - - protected override void ExecuteTask() - { - ExecuteTaskInternal(this.Status); - Status.SetFinishedMessage("The Plugin Library Catalogue was updated."); - } - - internal static void ExecuteTaskInternal(ScheduledTaskStatus Status) - { - PluginLibraryUpdateRequest updateRequestBody; - PluginLibraryUpdateResponse updateResult; - string catalogueFile; - PluginLibraryCompatibilityRequest compatRequestBody; - PluginLibraryCompatibilityResponse compatResult; - string compatibilityFile; - - var DiscoBIVersion = CurrentDiscoVersion(); - HttpWebRequest webRequest; - - #region Update - - Status.UpdateStatus(1, "Updating Plugin Library Catalogue", "Building Request"); - - using (DiscoDataContext database = new DiscoDataContext()) - { - catalogueFile = Plugins.CatalogueFile(database); - - updateRequestBody = new PluginLibraryUpdateRequest() - { - DeploymentId = database.DiscoConfiguration.DeploymentId, - HostVersion = typeof(Plugins).Assembly.GetName().Version.ToString(4) - }; - } - - Status.UpdateStatus(10, "Sending Request"); - - webRequest = (HttpWebRequest)HttpWebRequest.Create(PluginLibraryUpdateUrl()); - webRequest.KeepAlive = false; - - webRequest.ContentType = "application/xml"; - webRequest.Method = WebRequestMethods.Http.Post; - webRequest.UserAgent = string.Format("Disco/{0} (PluginLibrary)", DiscoBIVersion); - - using (var wrStream = webRequest.GetRequestStream()) - { - XmlSerializer xml = new XmlSerializer(typeof(PluginLibraryUpdateRequest)); - xml.Serialize(wrStream, updateRequestBody); - } - - Status.UpdateStatus(20, "Waiting for Response"); - - using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse()) - { - if (webResponse.StatusCode == HttpStatusCode.OK) - { - Status.UpdateStatus(45, "Reading Response"); - using (var wResStream = webResponse.GetResponseStream()) - { - XmlSerializer xml = new XmlSerializer(typeof(PluginLibraryUpdateResponse)); - updateResult = (PluginLibraryUpdateResponse)xml.Deserialize(wResStream); - } - } - else - { - Status.SetTaskException(new WebException(string.Format("Server responded with: [{0}] {1}", webResponse.StatusCode, webResponse.StatusDescription))); - return; - } - } - - if (!Directory.Exists(Path.GetDirectoryName(catalogueFile))) - Directory.CreateDirectory(Path.GetDirectoryName(catalogueFile)); - - using (FileStream fs = new FileStream(catalogueFile, FileMode.Create, FileAccess.Write, FileShare.None)) - { - using (StreamWriter fsWriter = new StreamWriter(fs)) - { - fsWriter.Write(JsonConvert.SerializeObject(updateResult)); - fsWriter.Flush(); - } - } - #endregion - - #region Compatibility - - Status.UpdateStatus(50, "Updating Plugin Library Compatibility", "Building Request"); - - using (DiscoDataContext database = new DiscoDataContext()) - { - compatibilityFile = Plugins.CompatibilityFile(database); - - compatRequestBody = new PluginLibraryCompatibilityRequest() - { - DeploymentId = database.DiscoConfiguration.DeploymentId, - HostVersion = typeof(Plugins).Assembly.GetName().Version.ToString(4) - }; - } - - Status.UpdateStatus(60, "Sending Request"); - - webRequest = (HttpWebRequest)HttpWebRequest.Create(PluginLibraryCompatibilityUrl()); - webRequest.KeepAlive = false; - - webRequest.ContentType = "application/json"; - webRequest.Method = WebRequestMethods.Http.Post; - webRequest.UserAgent = string.Format("Disco/{0} (PluginLibrary)", DiscoBIVersion); - - using (var wrStream = webRequest.GetRequestStream()) - { - XmlSerializer xml = new XmlSerializer(typeof(PluginLibraryCompatibilityRequest)); - xml.Serialize(wrStream, compatRequestBody); - } - - Status.UpdateStatus(70, "Waiting for Response"); - - using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse()) - { - if (webResponse.StatusCode == HttpStatusCode.OK) - { - Status.UpdateStatus(95, "Reading Response"); - using (var wResStream = webResponse.GetResponseStream()) - { - XmlSerializer xml = new XmlSerializer(typeof(PluginLibraryCompatibilityResponse)); - compatResult = (PluginLibraryCompatibilityResponse)xml.Deserialize(wResStream); - } - } - else - { - Status.SetTaskException(new WebException(string.Format("Server responded with: [{0}] {1}", webResponse.StatusCode, webResponse.StatusDescription))); - return; - } - } - - if (!Directory.Exists(Path.GetDirectoryName(compatibilityFile))) - Directory.CreateDirectory(Path.GetDirectoryName(compatibilityFile)); - - using (FileStream fs = new FileStream(compatibilityFile, FileMode.Create, FileAccess.Write, FileShare.None)) - { - using (StreamWriter fsWriter = new StreamWriter(fs)) - { - fsWriter.Write(JsonConvert.SerializeObject(compatResult)); - fsWriter.Flush(); - } - } - #endregion - } - - private static string PluginLibraryUpdateUrl() - { - return string.Concat(DiscoServiceHelpers.CommunityUrl(), "DiscoPluginLibrary/V1"); - } - private static string PluginLibraryCompatibilityUrl() - { - return string.Concat(DiscoServiceHelpers.CommunityUrl(), "DiscoPluginLibrary/CompatibilityV1"); - } - - public static ScheduledTaskStatus ScheduleNow() - { - - var taskStatus = ScheduledTasks.GetTaskStatuses(typeof(PluginLibraryUpdateTask)).Where(ts => ts.IsRunning).FirstOrDefault(); - if (taskStatus != null) - return taskStatus; - else - { - var t = new PluginLibraryUpdateTask(); - return t.ScheduleTask(); - } - } - public static ScheduledTaskStatus RunningStatus - { - get - { - return ScheduledTasks.GetTaskStatuses(typeof(PluginLibraryUpdateTask)).Where(ts => ts.IsRunning).FirstOrDefault(); - } - } - - public override void InitalizeScheduledTask(DiscoDataContext Database) - { - // Random time between midday and midnight. - var rnd = new Random(); - - var rndHour = rnd.Next(12, 23); - var rndMinute = rnd.Next(0, 59); - - TriggerBuilder triggerBuilder = TriggerBuilder.Create(). - WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(rndHour, rndMinute)); - - this.ScheduleTask(triggerBuilder); - } - } -} diff --git a/Disco.Services/Plugins/InstallPluginTask.cs b/Disco.Services/Plugins/InstallPluginTask.cs index 0b555d34..66c8d3c3 100644 --- a/Disco.Services/Plugins/InstallPluginTask.cs +++ b/Disco.Services/Plugins/InstallPluginTask.cs @@ -1,11 +1,13 @@ using Disco.Data.Repository; +using Disco.Models.Services.Interop.DiscoServices; +using Disco.Services.Interop.DiscoServices; using Disco.Services.Tasks; using Quartz; using System; using System.IO; using System.IO.Compression; using System.Linq; -using System.Net; +using System.Net.Http; namespace Disco.Services.Plugins { @@ -33,31 +35,17 @@ namespace Disco.Services.Plugins Directory.CreateDirectory(Path.GetDirectoryName(packageFilePath)); // Need to Download the Package - HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(packageUrlPath); - webRequest.KeepAlive = false; - - webRequest.ContentType = "application/xml"; - webRequest.Method = WebRequestMethods.Http.Get; - webRequest.UserAgent = string.Format("Disco/{0} (PluginLibrary)", Disco.Services.Plugins.CommunityInterop.PluginLibraryUpdateTask.CurrentDiscoVersion()); - - using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse()) + using (HttpClient httpClient = new HttpClient()) { - if (webResponse.StatusCode == HttpStatusCode.OK) + using (var httpResponse = httpClient.GetAsync(packageUrlPath).Result) { - Status.UpdateStatus(0, "Downloading..."); - using (var wResStream = webResponse.GetResponseStream()) + httpResponse.EnsureSuccessStatusCode(); + + using (FileStream fsOut = new FileStream(packageFilePath, FileMode.Create, FileAccess.Write, FileShare.None)) { - using (FileStream fsOut = new FileStream(packageFilePath, FileMode.Create, FileAccess.Write, FileShare.None)) - { - wResStream.CopyTo(fsOut); - } + httpResponse.Content.ReadAsStreamAsync().Result.CopyTo(fsOut); } } - else - { - Status.SetTaskException(new WebException(string.Format("Server responded with: [{0}] {1}", webResponse.StatusCode, webResponse.StatusDescription))); - return; - } } } @@ -97,10 +85,10 @@ namespace Disco.Services.Plugins string packagePath = Path.Combine(database.DiscoConfiguration.PluginsLocation, packageManifest.Id); // Check for Compatibility - var compatibilityData = Plugins.LoadCompatibilityData(database); - var pluginCompatibility = compatibilityData.Plugins.FirstOrDefault(i => i.Id.Equals(packageManifest.Id, StringComparison.OrdinalIgnoreCase) && 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)); + var libraryIncompatibility = PluginLibrary.LoadManifest(database).LoadIncompatibilityData(); + PluginIncompatibility incompatibility; + if (!libraryIncompatibility.IsCompatible(packageManifest.Id, packageManifest.Version, out incompatibility)) + throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] is not compatible: {2}", packageManifest.Id, packageManifest.VersionFormatted, incompatibility.Reason)); // Force Delete of Existing Folder if (Directory.Exists(packagePath)) diff --git a/Disco.Services/Plugins/Plugins.cs b/Disco.Services/Plugins/Plugins.cs index 48adbc77..2bce21dd 100644 --- a/Disco.Services/Plugins/Plugins.cs +++ b/Disco.Services/Plugins/Plugins.cs @@ -1,5 +1,6 @@ using Disco.Data.Repository; -using Disco.Models.BI.Interop.Community; +using Disco.Models.Services.Interop.DiscoServices; +using Disco.Services.Interop.DiscoServices; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -248,81 +249,6 @@ namespace Disco.Services.Plugins throw new InvalidOperationException(string.Format("Unknown Plugin Feature Category Type: [{0}]", FeatureCategoryType.Name)); } - public static string CatalogueFile(DiscoDataContext Database) - { - return Path.Combine(Database.DiscoConfiguration.PluginPackagesLocation, "Catalogue.json"); - } - public static string CompatibilityFile(DiscoDataContext Database) - { - return Path.Combine(Database.DiscoConfiguration.PluginPackagesLocation, "Compatibility.json"); - } - - public static PluginLibraryUpdateResponse LoadCatalogue(DiscoDataContext Database) - { - var catalogueFile = CatalogueFile(Database); - - if (!File.Exists(catalogueFile)) - return null; - - return JsonConvert.DeserializeObject(File.ReadAllText(catalogueFile)); - } - - public static PluginLibraryCompatibilityResponse LoadCompatibilityData(DiscoDataContext Database) - { - 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(Database); - - if (File.Exists(localCompatFile)) - { - Data = JsonConvert.DeserializeObject(File.ReadAllText(localCompatFile)); - Data.HostVersion = hostVersion.ToString(4); - } - if (File.Exists(serverCompatFile)) - { - var serverData = JsonConvert.DeserializeObject(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.OrdinalIgnoreCase) && 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(), - ResponseTimestamp = new DateTime(2011, 7, 1) - }; - } - - return Data; - } - public static void InitalizePlugins(DiscoDataContext Database) { if (_PluginManifests == null) @@ -332,7 +258,7 @@ namespace Disco.Services.Plugins if (_PluginManifests == null) { Version hostVersion = typeof(Plugins).Assembly.GetName().Version; - var compatibilityData = new Lazy(() => LoadCompatibilityData(Database)); + var compatibilityData = new Lazy(() => PluginLibrary.LoadManifest(Database).LoadIncompatibilityData()); Dictionary loadedPlugins = new Dictionary(); PluginPath = Database.DiscoConfiguration.PluginsLocation; @@ -371,9 +297,9 @@ namespace Disco.Services.Plugins if (pluginManifest != null) { // Check Version Compatibility - var pluginCompatibility = compatibilityData.Value.Plugins.FirstOrDefault(i => i.Id.Equals(pluginManifest.Id, StringComparison.OrdinalIgnoreCase) && 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)); + var pluginIncompatible = compatibilityData.Value.IncompatiblePlugins.FirstOrDefault(i => i.PluginId.Equals(pluginManifest.Id, StringComparison.OrdinalIgnoreCase) && pluginManifest.Version == i.Version); + if (pluginIncompatible != null) + throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] is not compatible: {2}", pluginManifest.Id, pluginManifest.VersionFormatted, pluginIncompatible.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())); @@ -441,13 +367,13 @@ namespace Disco.Services.Plugins _PluginAssemblyManifests = _PluginManifests.Values.ToDictionary(p => p.PluginAssembly, p => p); } - public static PluginManifest UpdatePlugin(DiscoDataContext Database, PluginManifest ExistingManifest, String UpdatePluginPackageFilePath, PluginLibraryCompatibilityResponse CompatibilityData = null) + public static PluginManifest UpdatePlugin(DiscoDataContext Database, PluginManifest ExistingManifest, String UpdatePluginPackageFilePath, PluginLibraryIncompatibility PluginLibraryIncompatibility = null) { PluginManifest updatedManifest; using (var packageStream = File.OpenRead(UpdatePluginPackageFilePath)) { - updatedManifest = UpdatePlugin(Database, ExistingManifest, packageStream, CompatibilityData); + updatedManifest = UpdatePlugin(Database, ExistingManifest, packageStream, PluginLibraryIncompatibility); } // Remove Update after processing @@ -456,7 +382,7 @@ namespace Disco.Services.Plugins return updatedManifest; } - public static PluginManifest UpdatePlugin(DiscoDataContext Database, PluginManifest ExistingManifest, Stream UpdatePluginPackage, PluginLibraryCompatibilityResponse CompatibilityData = null) + public static PluginManifest UpdatePlugin(DiscoDataContext Database, PluginManifest ExistingManifest, Stream UpdatePluginPackage, PluginLibraryIncompatibility PluginLibraryIncompatibility = null) { using (MemoryStream packageStream = new MemoryStream()) { @@ -486,11 +412,11 @@ namespace Disco.Services.Plugins } // Check Compatibility - if (CompatibilityData == null) - CompatibilityData = LoadCompatibilityData(Database); - var pluginCompatibility = CompatibilityData.Plugins.FirstOrDefault(i => i.Id.Equals(packageManifest.Id, StringComparison.OrdinalIgnoreCase) && 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)); + if (PluginLibraryIncompatibility == null) + PluginLibraryIncompatibility = PluginLibrary.LoadManifest(Database).LoadIncompatibilityData(); + var pluginIncompatibility = PluginLibraryIncompatibility.IncompatiblePlugins.FirstOrDefault(i => i.PluginId.Equals(packageManifest.Id, StringComparison.OrdinalIgnoreCase) && packageManifest.Version == i.Version); + if (pluginIncompatibility != null) + throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] is not compatible: {2}", packageManifest.Id, packageManifest.VersionFormatted, pluginIncompatibility.Reason)); string packagePath = Path.Combine(Database.DiscoConfiguration.PluginsLocation, packageManifest.Id); diff --git a/Disco.Services/Plugins/UpdatePluginTask.cs b/Disco.Services/Plugins/UpdatePluginTask.cs index 3fffc683..cb7020f2 100644 --- a/Disco.Services/Plugins/UpdatePluginTask.cs +++ b/Disco.Services/Plugins/UpdatePluginTask.cs @@ -1,17 +1,14 @@ -using System; +using Disco.Data.Repository; +using Disco.Models.Services.Interop.DiscoServices; +using Disco.Services.Interop.DiscoServices; +using Disco.Services.Tasks; +using Quartz; +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; +using System.Net.Http; namespace Disco.Services.Plugins { @@ -24,17 +21,19 @@ namespace Disco.Services.Plugins string pluginId = (string)this.ExecutionContext.JobDetail.JobDataMap["PluginId"]; string packageFilePath = (string)this.ExecutionContext.JobDetail.JobDataMap["PackageFilePath"]; - PluginLibraryUpdateResponse catalogue; + PluginLibraryManifestV2 libraryManifest; + PluginLibraryIncompatibility libraryIncompatibility; string pluginPackagesLocation; if (!Plugins.PluginsLoaded) throw new InvalidOperationException("Plugins have not been initialized"); - List> updatePlugins; + List> updatePlugins; using (DiscoDataContext database = new DiscoDataContext()) { - catalogue = Plugins.LoadCatalogue(database); + libraryManifest = PluginLibrary.LoadManifest(database); + libraryIncompatibility = libraryManifest.LoadIncompatibilityData(); pluginPackagesLocation = database.DiscoConfiguration.PluginPackagesLocation; } @@ -44,30 +43,41 @@ namespace Disco.Services.Plugins { // Update Single from Catalogue PluginManifest existingManifest = Plugins.GetPlugin(pluginId); - var catalogueItem = catalogue.Plugins.FirstOrDefault(p => p.Id == existingManifest.Id); + var libraryItem = libraryManifest.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) + if (libraryItem == null) + throw new InvalidOperationException("This item isn't in the plugin library manifest"); + + var libraryItemRelease = libraryItem.LatestCompatibleRelease(libraryIncompatibility); + + if (Version.Parse(libraryItemRelease.Version) <= existingManifest.Version) throw new InvalidOperationException("Only newer versions can be used to update a plugin"); - updatePlugins = new List>() { - new Tuple(existingManifest, null, catalogueItem) + updatePlugins = new List>() { + Tuple.Create(existingManifest, (string)null, libraryItem, libraryItemRelease) }; } else { // Update Single from Local PluginManifest existingManifest = Plugins.GetPlugin(pluginId); - updatePlugins = new List>() { - new Tuple(existingManifest, packageFilePath, null) + updatePlugins = new List>() { + Tuple.Create(existingManifest, packageFilePath, (PluginLibraryItemV2)null, (PluginLibraryItemReleaseV2)null) }; } } else { // Update All - updatePlugins = Plugins.GetPlugins().Join((IEnumerable)catalogue.Plugins, manifest => manifest.Id, update => update.Id, (manifest, update) => new Tuple(manifest, null, update)).Where(i => Version.Parse(i.Item3.LatestVersion) > i.Item1.Version).ToList(); + updatePlugins = Plugins.GetPlugins() + .Join( + libraryManifest.Plugins, + manifest => manifest.Id, + libraryItem => libraryItem.Id, + (manifest, libraryItem) => Tuple.Create(manifest, (string)null, libraryItem, libraryItem.LatestCompatibleRelease(libraryIncompatibility)), + StringComparer.OrdinalIgnoreCase) + .Where(i => Version.Parse(i.Item4.Version) > i.Item1.Version) + .ToList(); } if (updatePlugins == null || updatePlugins.Count == 0) @@ -115,31 +125,40 @@ namespace Disco.Services.Plugins internal static void UpdateOffline(ScheduledTaskStatus Status) { - PluginLibraryUpdateResponse pluginCatalogue = null; + PluginLibraryManifestV2 libraryManifest = null; + PluginLibraryIncompatibility libraryIncompatibility = null; List installedPluginManifests; string pluginPackagesLocation; - List> updatePlugins = new List>(); + List> updatePlugins = + new List>(); + - using (DiscoDataContext database = new DiscoDataContext()) { pluginPackagesLocation = database.DiscoConfiguration.PluginPackagesLocation; installedPluginManifests = OfflineInstalledPlugins(database); - if (installedPluginManifests.Count > 0) - pluginCatalogue = Plugins.LoadCatalogue(database); + if (installedPluginManifests.Count > 0){ + libraryManifest = PluginLibrary.LoadManifest(database); + libraryIncompatibility = libraryManifest.LoadIncompatibilityData(); + } } - if (pluginCatalogue != null && installedPluginManifests.Count > 0) + if (libraryManifest != null && installedPluginManifests.Count > 0) { foreach (var pluginManifest in installedPluginManifests) { // Check for Update - var catalogueItem = pluginCatalogue.Plugins.FirstOrDefault(i => i.Id == pluginManifest.Id && Version.Parse(i.LatestVersion) > pluginManifest.Version); + var libraryItem = libraryManifest.Plugins.FirstOrDefault(i => i.Id == pluginManifest.Id); - if (catalogueItem != null) - { // Update Available - updatePlugins.Add(new Tuple(pluginManifest, null, catalogueItem)); + if (libraryItem != null) + { + var libraryItemRelease = libraryItem.LatestCompatibleRelease(libraryIncompatibility); + + if (libraryItemRelease != null && Version.Parse(libraryItemRelease.Version) > pluginManifest.Version) + { // Update Available + updatePlugins.Add(Tuple.Create(pluginManifest, (string)null, libraryItem, libraryItemRelease)); + } } } } @@ -150,18 +169,19 @@ namespace Disco.Services.Plugins } } - internal static void ExecuteTaskInternal(ScheduledTaskStatus Status, string pluginPackagesLocation, List> UpdatePlugins) + internal static void ExecuteTaskInternal(ScheduledTaskStatus Status, string pluginPackagesLocation, List> UpdatePlugins) { while (UpdatePlugins.Count > 0) { var updatePlugin = UpdatePlugins[0]; var existingManifest = updatePlugin.Item1; var packageTempFilePath = updatePlugin.Item2; - var catalogueItem = updatePlugin.Item3; + var libraryItem = updatePlugin.Item3; + var libraryItemRelease = updatePlugin.Item4; UpdatePlugins.Remove(updatePlugin); - var pluginId = existingManifest != null ? existingManifest.Id : catalogueItem.Id; - var pluginName = existingManifest != null ? existingManifest.Name : catalogueItem.Name; + var pluginId = existingManifest != null ? existingManifest.Id : libraryItemRelease.PluginId; + var pluginName = existingManifest != null ? existingManifest.Name : libraryItem.Name; if (string.IsNullOrEmpty(packageTempFilePath)) { @@ -176,31 +196,19 @@ namespace Disco.Services.Plugins Directory.CreateDirectory(Path.GetDirectoryName(packageTempFilePath)); // Need to Download the Package - HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(catalogueItem.LatestDownloadUrl); - webRequest.KeepAlive = false; - - webRequest.ContentType = "application/xml"; - webRequest.Method = WebRequestMethods.Http.Get; - webRequest.UserAgent = string.Format("Disco/{0} (PluginLibrary)", Disco.Services.Plugins.CommunityInterop.PluginLibraryUpdateTask.CurrentDiscoVersion()); - - using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse()) + using (HttpClient httpClient = new HttpClient()) { - if (webResponse.StatusCode == HttpStatusCode.OK) + Status.UpdateStatus(0, "Downloading..."); + + using (var httpResponse = httpClient.GetAsync(libraryItemRelease.DownloadUrl).Result) { - Status.UpdateStatus(0, "Downloading..."); - using (var wResStream = webResponse.GetResponseStream()) + httpResponse.EnsureSuccessStatusCode(); + + using (FileStream fsOut = new FileStream(packageTempFilePath, FileMode.Create, FileAccess.Write, FileShare.None)) { - using (FileStream fsOut = new FileStream(packageTempFilePath, FileMode.Create, FileAccess.Write, FileShare.None)) - { - wResStream.CopyTo(fsOut); - } + httpResponse.Content.ReadAsStreamAsync().Result.CopyTo(fsOut); } } - else - { - Status.SetTaskException(new WebException(string.Format("Server responded with: [{0}] {1}", webResponse.StatusCode, webResponse.StatusDescription))); - return; - } } } @@ -234,10 +242,10 @@ namespace Disco.Services.Plugins using (DiscoDataContext database = new DiscoDataContext()) { // Check for Compatibility - var compatibilityData = Plugins.LoadCompatibilityData(database); - var pluginCompatibility = compatibilityData.Plugins.FirstOrDefault(i => i.Id.Equals(updateManifest.Id, StringComparison.OrdinalIgnoreCase) && 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 incompatibilityLibrary = PluginLibrary.LoadManifest(database).LoadIncompatibilityData(); + PluginIncompatibility incompatibility; + if (!incompatibilityLibrary.IsCompatible(updateManifest.Id, updateManifest.Version, out incompatibility)) + throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] is not compatible: {2}", updateManifest.Id, updateManifest.VersionFormatted, incompatibility.Reason)); var updatePluginPath = Path.Combine(database.DiscoConfiguration.PluginsLocation, string.Format("{0}.discoPlugin", updateManifest.Id)); File.Move(packageTempFilePath, updatePluginPath); diff --git a/Disco.Services/Plugins/UpdatePluginsAfterDiscoUpdateTask.cs b/Disco.Services/Plugins/UpdatePluginsAfterDiscoUpdateTask.cs index 097c428b..1b6b01e2 100644 --- a/Disco.Services/Plugins/UpdatePluginsAfterDiscoUpdateTask.cs +++ b/Disco.Services/Plugins/UpdatePluginsAfterDiscoUpdateTask.cs @@ -1,9 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Disco.Data.Repository; +using Disco.Services.Interop.DiscoServices; using Disco.Services.Tasks; +using System; +using System.Linq; namespace Disco.Services.Plugins { @@ -20,12 +19,15 @@ namespace Disco.Services.Plugins // Wait for App to Load (10 Seconds) for (int i = 0; i < 10; i++) { - this.Status.UpdateStatus(10 * i); + this.Status.UpdateStatus(10 * i); System.Threading.Thread.Sleep(1000); } // Update Catalogue - CommunityInterop.PluginLibraryUpdateTask.ExecuteTaskInternal(this.Status); + using (DiscoDataContext database = new DiscoDataContext()) + { + PluginLibrary.UpdateManifest(database, this.Status); + } // Update all Plugins UpdatePluginTask.UpdateOffline(this.Status); diff --git a/Disco.Web/Areas/API/Controllers/PluginController.cs b/Disco.Web/Areas/API/Controllers/PluginController.cs index 06260a2c..636783cf 100644 --- a/Disco.Web/Areas/API/Controllers/PluginController.cs +++ b/Disco.Web/Areas/API/Controllers/PluginController.cs @@ -1,6 +1,6 @@ using Disco.Services.Authorization; +using Disco.Services.Interop.DiscoServices; using Disco.Services.Plugins; -using Disco.Services.Plugins.CommunityInterop; using Disco.Services.Tasks; using Disco.Services.Web; using System; @@ -15,7 +15,7 @@ namespace Disco.Web.Areas.API.Controllers public partial class PluginController : AuthorizedDatabaseController { [DiscoAuthorize(Claims.Config.Plugin.Install)] - public virtual ActionResult UpdateLibraryCatalogue(bool TryWaitingForCompletion = false) + public virtual ActionResult UpdateLibraryManifest(bool TryWaitingForCompletion = false) { var status = PluginLibraryUpdateTask.ScheduleNow(); @@ -93,19 +93,25 @@ namespace Disco.Web.Areas.API.Controllers if (string.IsNullOrEmpty(PluginId)) throw new ArgumentNullException("PluginId", "A PluginId must be supplied"); - var catalogue = Plugins.LoadCatalogue(Database); - var plugin = catalogue.Plugins.FirstOrDefault(p => p.Id.Equals(PluginId)); + var library = PluginLibrary.LoadManifest(Database); + var libraryIncompatibility = library.LoadIncompatibilityData(); + var libraryItem = library.Plugins.FirstOrDefault(p => p.Id.Equals(PluginId)); - if (plugin == null) - throw new ArgumentNullException("PluginId", "Plugin not found in catalogue"); + if (libraryItem == null) + throw new ArgumentNullException("PluginId", "Plugin not found in library"); + + var libraryItemRelease = libraryItem.LatestCompatibleRelease(libraryIncompatibility); + + if (libraryItemRelease == null) + throw new ArgumentNullException("PluginId", "No compatibility releases were found in library"); // Already Installed? - if (Plugins.PluginInstalled(plugin.Id)) + if (Plugins.PluginInstalled(libraryItem.Id)) throw new InvalidOperationException("This plugin is already installed"); - var tempPluginLocation = Path.Combine(Database.DiscoConfiguration.PluginPackagesLocation, string.Format("{0}.discoPlugin", plugin.Id)); + var tempPluginLocation = Path.Combine(Database.DiscoConfiguration.PluginPackagesLocation, string.Format("{0}.discoPlugin", libraryItem.Id)); - var status = InstallPluginTask.InstallPlugin(plugin.LatestDownloadUrl, tempPluginLocation, true); + var status = InstallPluginTask.InstallPlugin(libraryItem.LatestCompatibleRelease(libraryIncompatibility).DownloadUrl, tempPluginLocation, true); return RedirectToAction(MVC.Config.Logging.TaskStatus(status.SessionId)); } diff --git a/Disco.Web/Areas/Config/Controllers/PluginsController.cs b/Disco.Web/Areas/Config/Controllers/PluginsController.cs index da36119f..77fc9638 100644 --- a/Disco.Web/Areas/Config/Controllers/PluginsController.cs +++ b/Disco.Web/Areas/Config/Controllers/PluginsController.cs @@ -1,4 +1,5 @@ using Disco.Services.Authorization; +using Disco.Services.Interop.DiscoServices; using Disco.Services.Plugins; using Disco.Services.Users; using Disco.Services.Web; @@ -16,7 +17,7 @@ namespace Disco.Web.Areas.Config.Controllers Models.Plugins.IndexViewModel vm = new Models.Plugins.IndexViewModel() { PluginManifests = Plugins.GetPlugins(), - Catalogue = Plugins.LoadCatalogue(Database) + PluginLibrary = PluginLibrary.LoadManifest(Database) }; return View(vm); } @@ -70,18 +71,18 @@ namespace Disco.Web.Areas.Config.Controllers public virtual ActionResult Install() { // Check for recent catalogue - var catalogue = Plugins.LoadCatalogue(Database); + var library = PluginLibrary.LoadManifest(Database); - if (catalogue == null || catalogue.ResponseTimestamp < DateTime.Now.AddHours(-1)) + if (library == null || library.ManifestDate < DateTime.Now.AddHours(-1)) { // Need to Update Catalogue (over 1 hour old) - return RedirectToAction(MVC.API.Plugin.UpdateLibraryCatalogue(true)); + return RedirectToAction(MVC.API.Plugin.UpdateLibraryManifest(true)); } else { var model = new Models.Plugins.InstallModel() { - Catalogue = catalogue + Library = library }; return View(model); diff --git a/Disco.Web/Areas/Config/Models/Plugins/IndexViewModel.cs b/Disco.Web/Areas/Config/Models/Plugins/IndexViewModel.cs index 7f726652..b083fb3a 100644 --- a/Disco.Web/Areas/Config/Models/Plugins/IndexViewModel.cs +++ b/Disco.Web/Areas/Config/Models/Plugins/IndexViewModel.cs @@ -1,33 +1,43 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using Disco.Models.BI.Interop.Community; +using Disco.Models.Services.Interop.DiscoServices; +using Disco.Services.Interop.DiscoServices; using Disco.Services.Plugins; using Disco.Services.Plugins.Features.Other; +using System; +using System.Collections.Generic; +using System.Linq; namespace Disco.Web.Areas.Config.Models.Plugins { public class IndexViewModel { public List PluginManifests { get; set; } - public PluginLibraryUpdateResponse Catalogue { get; set; } + public PluginLibraryManifestV2 PluginLibrary { get; set; } - private Dictionary _PluginUpdates; - public Dictionary PluginUpdates + private Dictionary> _PluginUpdates; + public Dictionary> PluginUpdates { get { if (_PluginUpdates == null) { - if (Catalogue == null || Catalogue.Plugins == null || Catalogue.Plugins.Count == 0 || + if (PluginLibrary == null || PluginLibrary.Plugins == null || PluginLibrary.Plugins.Count == 0 || PluginManifests == null || PluginManifests.Count == 0) { - _PluginUpdates = new Dictionary(); // No Updates + _PluginUpdates = new Dictionary>(); // No Updates } else { - _PluginUpdates = PluginManifests.Join((IEnumerable)Catalogue.Plugins, manifest => manifest.Id, update => update.Id, (manifest, update) => new Tuple(manifest, update)).Where(i => Version.Parse(i.Item2.LatestVersion) > i.Item1.Version).ToDictionary(i => i.Item1, i => i.Item2); + var libraryIncompatibility = PluginLibrary.LoadIncompatibilityData(); + + _PluginUpdates = PluginManifests + .Join( + PluginLibrary.Plugins, + manifest => manifest.Id, + libraryItem => libraryItem.Id, + (manifest, libraryItem) => Tuple.Create(manifest, libraryItem, libraryItem.LatestCompatibleRelease(libraryIncompatibility)), + StringComparer.OrdinalIgnoreCase) + .Where(i => i.Item3 != null && Version.Parse(i.Item3.Version) > i.Item1.Version) + .ToDictionary(i => i.Item1, i => Tuple.Create(i.Item2, i.Item3)); } } return _PluginUpdates; diff --git a/Disco.Web/Areas/Config/Models/Plugins/InstallModel.cs b/Disco.Web/Areas/Config/Models/Plugins/InstallModel.cs index 8f59c960..31a99d4f 100644 --- a/Disco.Web/Areas/Config/Models/Plugins/InstallModel.cs +++ b/Disco.Web/Areas/Config/Models/Plugins/InstallModel.cs @@ -1,13 +1,29 @@ -using System; +using Disco.Models.Services.Interop.DiscoServices; +using Disco.Services.Interop.DiscoServices; +using System; using System.Collections.Generic; using System.Linq; -using System.Web; -using Disco.Models.BI.Interop.Community; namespace Disco.Web.Areas.Config.Models.Plugins { public class InstallModel { - public PluginLibraryUpdateResponse Catalogue { get; set; } + public PluginLibraryManifestV2 Library { get; set; } + + public List>>> AvailablePlugins + { + get + { + var incompatibility = Library.LoadIncompatibilityData(); + + return Library.Plugins + .Select(p => Tuple.Create(p, p.LatestCompatibleRelease(incompatibility))) + .Where(p => p.Item2 != null) + .GroupBy(p => p.Item1.PrimaryFeatureCategory) + .Select(p => Tuple.Create(p.Key, p.OrderBy(r => r.Item1.Name).ToList())) + .OrderBy(g => g.Item1) + .ToList(); + } + } } } \ No newline at end of file diff --git a/Disco.Web/Areas/Config/Models/Plugins/PluginConfigurationViewModel.cs b/Disco.Web/Areas/Config/Models/Plugins/PluginConfigurationViewModel.cs index 2576f8b1..c05b67f2 100644 --- a/Disco.Web/Areas/Config/Models/Plugins/PluginConfigurationViewModel.cs +++ b/Disco.Web/Areas/Config/Models/Plugins/PluginConfigurationViewModel.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using Disco.Services.Plugins; +using Disco.Services.Plugins; +using System; namespace Disco.Web.Areas.Config.Models.Plugins { diff --git a/Disco.Web/Areas/Config/Views/Plugins/Install.cshtml b/Disco.Web/Areas/Config/Views/Plugins/Install.cshtml index f31ee36c..b72c5e43 100644 --- a/Disco.Web/Areas/Config/Views/Plugins/Install.cshtml +++ b/Disco.Web/Areas/Config/Views/Plugins/Install.cshtml @@ -7,10 +7,9 @@ ViewBag.Title = Html.ToBreadcrumb("Configuration", MVC.Config.Config.Index(), "Plugins", MVC.Config.Plugins.Index(), "Install Plugin"); } -
-

The plugin catalogue [https://discoict.com.au] was last updated @CommonHelpers.FriendlyDate((Model.Catalogue.ResponseTimestamp > DateTime.Now ? DateTime.Now : Model.Catalogue.ResponseTimestamp)) -

- @if (Model.Catalogue.Plugins.Count == 0) +
+

The plugin library [https://discoict.com.au] was last updated @CommonHelpers.FriendlyDate((Model.Library.ManifestDate > DateTime.Now ? DateTime.Now : Model.Library.ManifestDate))

+ @if (Model.Library.Plugins.Count == 0) {

No Plugins are Available

@@ -18,50 +17,50 @@ } else { - var plugins = Model.Catalogue.Plugins; - int itemsPerColumn = plugins.Count / 3; - var itemNextId = 0; - - - @for (int i = 0; i < 3; i++) - { - +
+ } +
+ }
@@ -98,6 +97,8 @@ var $selectedPlugin; var $selectedPluginUrl; + $('#pluginLibraryHeading').appendTo('#layout_PageHeading'); + // Install var $dialogInstall = $('#dialogInstallPlugin').dialog({ resizable: false, @@ -120,7 +121,7 @@ } } }); - $('#pageMenu').find('a.pluginInstallLink').click(function () { + $('#pluginLibraryGroups').find('a.pluginInstallLink').click(function () { $this = $(this); $selectedPlugin = $this.closest('.pluginItem'); @@ -134,7 +135,7 @@ return false; }); - $('#pageMenu').find('a.pluginUpdateLink').click(function () { + $('#pluginLibraryGroups').find('a.pluginUpdateLink').click(function () { $this = $(this); $selectedPlugin = $this.closest('.pluginItem'); @@ -180,7 +181,7 @@ });
- @Html.ActionLinkButton("Update Catalogue", MVC.API.Plugin.UpdateLibraryCatalogue()) + @Html.ActionLinkButton("Update Plugin Library", MVC.API.Plugin.UpdateLibraryManifest()) @if (canInstallLocal) { @Html.ActionLinkButton("Install Plugin Package", MVC.API.Plugin.InstallLocal(), "buttonUpload") diff --git a/Disco.Web/Areas/Config/Views/Plugins/Install.generated.cs b/Disco.Web/Areas/Config/Views/Plugins/Install.generated.cs index e8c8b97a..0c17860f 100644 --- a/Disco.Web/Areas/Config/Views/Plugins/Install.generated.cs +++ b/Disco.Web/Areas/Config/Views/Plugins/Install.generated.cs @@ -64,13 +64,13 @@ namespace Disco.Web.Areas.Config.Views.Plugins #line hidden WriteLiteral("\r\n\r\n The plugin catalogue [The plugin library [https://discoict.com.au] was last updated "); #line 11 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - Write(CommonHelpers.FriendlyDate((Model.Catalogue.ResponseTimestamp > DateTime.Now ? DateTime.Now : Model.Catalogue.ResponseTimestamp))); + Write(CommonHelpers.FriendlyDate((Model.Library.ManifestDate > DateTime.Now ? DateTime.Now : Model.Library.ManifestDate))); #line default #line hidden -WriteLiteral("\r\n \r\n"); +WriteLiteral("\r\n"); - #line 13 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + #line 12 "..\..\Areas\Config\Views\Plugins\Install.cshtml" #line default #line hidden - #line 13 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - if (Model.Catalogue.Plugins.Count == 0) + #line 12 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + if (Model.Library.Plugins.Count == 0) { @@ -108,73 +108,81 @@ WriteLiteral(" style=\"width: 450px; padding: 100px 0;\""); WriteLiteral(">\r\n

No Plugins are Available

\r\n
\r\n"); - #line 18 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + #line 17 "..\..\Areas\Config\Views\Plugins\Install.cshtml" } else { - var plugins = Model.Catalogue.Plugins; - int itemsPerColumn = plugins.Count / 3; - var itemNextId = 0; + var pluginGroups = Model.AvailablePlugins; + #line default #line hidden -WriteLiteral(" \r\n \r\n"); +WriteLiteral(">\r\n"); + + + #line 23 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + + + #line default + #line hidden + + #line 23 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + foreach (var pluginGroup in pluginGroups) + { + + + #line default + #line hidden +WriteLiteral(" \r\n

"); #line 26 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - - - #line default - #line hidden - - #line 26 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - for (int i = 0; i < 3; i++) - { + Write(pluginGroup.Item1); #line default #line hidden -WriteLiteral(" \r\n"); +WriteLiteral("

\r\n \r\n"); - #line 29 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + #line 28 "..\..\Areas\Config\Views\Plugins\Install.cshtml" #line default #line hidden - #line 29 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - - int itemsForThisColumn = itemsPerColumn + (plugins.Count % 3 > i ? 1 : 0); - for (int i2 = 0; i2 < itemsForThisColumn && itemNextId < plugins.Count; i2++) - { - var plugin = plugins[itemNextId]; - itemNextId++; - var installedPlugin = Plugins.PluginInstalled(plugin.Id) ? Plugins.GetPlugin(plugin.Id) : null; + #line 28 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + foreach (var plugin in pluginGroup.Item2) + { + var installedPlugin = Plugins.PluginInstalled(plugin.Item1.Id) ? Plugins.GetPlugin(plugin.Item1.Id) : null; #line default #line hidden -WriteLiteral(" \r\n \r\n " + +" \r\n"); - #line 59 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - } - - - #line default - #line hidden -WriteLiteral("\r\n \r\n"); - - - #line 62 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - } + #line 58 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + } #line default #line hidden -WriteLiteral(" \r\n
\r\n " + +" (installedPlugin != null ? " pluginInstalled" : string.Empty + #line 33 "..\..\Areas\Config\Views\Plugins\Install.cshtml" +, Tuple.Create(Tuple.Create("", 1524), Tuple.Create(installedPlugin != null ? " pluginInstalled" : string.Empty #line default #line hidden -, 1693), false) +, 1524), false) ); -WriteLiteral(">\r\n \r\n "); - #line 37 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - Write(plugin.Name); + #line 34 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + Write(plugin.Item1.Name); #line default @@ -194,72 +202,72 @@ WriteLiteral(">"); WriteLiteral("\r\n"); - #line 38 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - + #line 35 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + #line default #line hidden - #line 38 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - if (installedPlugin == null) - { + #line 35 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + if (installedPlugin == null) + { #line default #line hidden -WriteLiteral(" (Url.Action(MVC.API.Plugin.Install(plugin.Id)) + #line 37 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + , Tuple.Create(Tuple.Create("", 1911), Tuple.Create(Url.Action(MVC.API.Plugin.Install(plugin.Item1.Id)) #line default #line hidden -, 2042), false) +, 1911), false) ); WriteLiteral(">Install\r\n"); - #line 41 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - } - else - { - if (Version.Parse(plugin.LatestVersion) > installedPlugin.Version) - { + #line 38 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + } + else + { + if (Version.Parse(plugin.Item2.Version) > installedPlugin.Version) + { #line default #line hidden -WriteLiteral(" (Url.Action(MVC.API.Plugin.Update(plugin.Id)) + #line 43 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + , Tuple.Create(Tuple.Create("", 2380), Tuple.Create(Url.Action(MVC.API.Plugin.Update(plugin.Item1.Id)) #line default #line hidden -, 2457), false) +, 2380), false) ); WriteLiteral(">Update \r\n"); - #line 47 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - } - else - { + #line 44 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + } + else + { #line default #line hidden -WriteLiteral(" Installed \r\n"); - #line 51 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - } - } + #line 48 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + } + } #line default #line hidden -WriteLiteral(" \r\n \r\n " + +" "); - #line 54 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - Write(new HtmlString(plugin.Blurb)); + #line 51 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + Write(new HtmlString(plugin.Item1.Description)); #line default #line hidden -WriteLiteral("\r\n \r\n \r\n \r\n "); - #line 56 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - Write(plugin.Id); + #line 53 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + Write(plugin.Item1.Id); #line default @@ -312,8 +321,8 @@ WriteLiteral(" class=\"pluginVersion\""); WriteLiteral(">v"); - #line 56 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - Write(plugin.LatestVersion); + #line 53 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + Write(plugin.Item2.Version); #line default @@ -321,22 +330,22 @@ WriteLiteral(">v"); WriteLiteral(" | "); - #line 56 "..\..\Areas\Config\Views\Plugins\Install.cshtml" - Write(plugin.Author); + #line 53 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + Write(plugin.Item1.Author); #line default #line hidden WriteLiteral(" | (plugin.Url + #line 53 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + , Tuple.Create(Tuple.Create("", 3251), Tuple.Create(plugin.Item1.InformationUrl #line default #line hidden -, 3218), false) +, 3251), false) ); WriteLiteral(" title=\"More Information\""); @@ -347,29 +356,31 @@ WriteLiteral(">\r\n \r\n \r\n"); +WriteLiteral(">\r\n \r\n " + +" \r\n
\r\n"); +WriteLiteral(" \r\n
\r\n"); - #line 65 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + #line 61 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + } + + + #line default + #line hidden +WriteLiteral(" \r\n"); + + + #line 63 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + } @@ -406,7 +417,7 @@ WriteLiteral(">Warning: All plugins run with the same level "om a trusted source.\r\n

\r\n \r\n\r\n"); - #line 78 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + #line 77 "..\..\Areas\Config\Views\Plugins\Install.cshtml" if (canInstallLocal) { @@ -426,13 +437,13 @@ WriteLiteral(" style=\"padding-bottom: 10px;\""); WriteLiteral(">\r\n"); - #line 82 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + #line 81 "..\..\Areas\Config\Views\Plugins\Install.cshtml" #line default #line hidden - #line 82 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + #line 81 "..\..\Areas\Config\Views\Plugins\Install.cshtml" using (Html.BeginForm(MVC.API.Plugin.InstallLocal(), FormMethod.Post, new { enctype = "multipart/form-data" })) { @@ -456,7 +467,7 @@ WriteLiteral(" type=\"file\""); WriteLiteral(" />\r\n"); - #line 86 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + #line 85 "..\..\Areas\Config\Views\Plugins\Install.cshtml" } @@ -480,48 +491,49 @@ WriteLiteral(">Warning: All plugins run with the same level "\n"); - #line 95 "..\..\Areas\Config\Views\Plugins\Install.cshtml" + #line 94 "..\..\Areas\Config\Views\Plugins\Install.cshtml" } #line default #line hidden WriteLiteral("