diff --git a/src/Umbraco.Core/Manifest/IManifestParser.cs b/src/Umbraco.Core/Manifest/IManifestParser.cs index 371ec54dae..dc3a19714e 100644 --- a/src/Umbraco.Core/Manifest/IManifestParser.cs +++ b/src/Umbraco.Core/Manifest/IManifestParser.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace Umbraco.Cms.Core.Manifest { public interface IManifestParser @@ -14,5 +16,11 @@ namespace Umbraco.Cms.Core.Manifest /// Parses a manifest. /// PackageManifest ParseManifest(string text); + + /// + /// Returns all package individual manifests + /// + /// + IEnumerable GetManifests(); } } diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index 1a533b1f24..07adc33f3c 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.Serialization; using Umbraco.Cms.Core.PropertyEditors; @@ -10,6 +10,19 @@ namespace Umbraco.Cms.Core.Manifest [DataContract] public class PackageManifest { + [DataMember(Name = "name", IsRequired = true)] + public string PackageName { get; set; } + + [DataMember(Name = "packageView", IsRequired = true)] + public string PackageView { get; set; } + + // TODO: iconUrl? since we cannot retrieve from nuget + + // TODO: Version since we cannot retrieve from nuget + + //[DataMember(Name = "name", IsRequired = true)] + //public Guid PackageId { get; set; } + /// /// Gets the source path of the manifest. /// diff --git a/src/Umbraco.Core/Packaging/InstalledPackage.cs b/src/Umbraco.Core/Packaging/InstalledPackage.cs new file mode 100644 index 0000000000..170d2c0156 --- /dev/null +++ b/src/Umbraco.Core/Packaging/InstalledPackage.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Runtime.Serialization; + +namespace Umbraco.Cms.Core.Packaging +{ + [DataContract(Name = "installedPackage")] + public class InstalledPackage + { + [DataMember(Name = "name", IsRequired = true)] + [Required] + public string PackageName { get; set; } + + // TODO: Version? Icon? Other metadata? + + [DataMember(Name = "packageView")] + public string PackageView { get; set; } + + [DataMember(Name = "plans")] + public IEnumerable PackageMigrationPlans { get; set; } = Enumerable.Empty(); + + [DataMember(Name = "hasPendingMigrations")] + public bool HasPendingMigrations => PackageMigrationPlans.Any(x => x.HasPendingMigrations); + } + +} diff --git a/src/Umbraco.Core/Packaging/InstalledPackageMigrationPlans.cs b/src/Umbraco.Core/Packaging/InstalledPackageMigrationPlans.cs new file mode 100644 index 0000000000..6bb060b429 --- /dev/null +++ b/src/Umbraco.Core/Packaging/InstalledPackageMigrationPlans.cs @@ -0,0 +1,27 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Cms.Core.Packaging +{ + [DataContract(Name = "installedPackageMigrations")] + public class InstalledPackageMigrationPlans + { + [DataMember(Name = "hasPendingMigrations")] + public bool HasPendingMigrations => FinalMigrationId != CurrentMigrationId; + + /// + /// If the package has migrations, this will be it's final migration Id + /// + /// + /// This can be used to determine if the package advertises any migrations + /// + [DataMember(Name = "finalMigrationId")] + public string FinalMigrationId { get; set; } + + /// + /// If the package has migrations, this will be it's current migration Id + /// + [DataMember(Name = "currentMigrationId")] + public string CurrentMigrationId { get; set; } + } + +} diff --git a/src/Umbraco.Core/Packaging/PackageDefinition.cs b/src/Umbraco.Core/Packaging/PackageDefinition.cs index 41f407237c..3c808d4de0 100644 --- a/src/Umbraco.Core/Packaging/PackageDefinition.cs +++ b/src/Umbraco.Core/Packaging/PackageDefinition.cs @@ -7,7 +7,13 @@ using Umbraco.Cms.Core.Models.Packaging; namespace Umbraco.Cms.Core.Packaging { - // This is the thing that goes in the createdPackages.config + + /// + /// A created package in the back office. + /// + /// + /// This data structure is persisted to createdPackages.config when creating packages in the back office. + /// [DataContract(Name = "packageInstance")] public class PackageDefinition { diff --git a/src/Umbraco.Core/Packaging/PackageMigrationPlan.cs b/src/Umbraco.Core/Packaging/PackageMigrationPlan.cs index 71c333d1cf..b7de5fa438 100644 --- a/src/Umbraco.Core/Packaging/PackageMigrationPlan.cs +++ b/src/Umbraco.Core/Packaging/PackageMigrationPlan.cs @@ -1,22 +1,38 @@ -using System; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Migrations; -using Umbraco.Extensions; namespace Umbraco.Cms.Core.Packaging -{ +{ /// /// Base class for package migration plans /// public abstract class PackageMigrationPlan : MigrationPlan, IDiscoverable { - protected PackageMigrationPlan(string name) : base(name) + /// + /// Creates a package migration plan + /// + /// The name of the package. If the package has a package.manifest these must match. + protected PackageMigrationPlan(string packageName) : this(packageName, packageName) + { + + } + + /// + /// Create a plan for a Package Name + /// + /// The package name that the plan is for. If the package has a package.manifest these must match. + /// + /// The plan name for the package. This should be the same name as the + /// package name if there is only one plan in the package. + /// + protected PackageMigrationPlan(string packageName, string planName) : base(planName) { // A call to From must be done first From(string.Empty); DefinePlan(); + PackageName = packageName; } /// @@ -25,6 +41,11 @@ namespace Umbraco.Cms.Core.Packaging /// public override bool IgnoreCurrentState => true; + /// + /// Returns the Package Name for this plan + /// + public string PackageName { get; } + protected abstract void DefinePlan(); } diff --git a/src/Umbraco.Core/Packaging/PendingPackageMigrations.cs b/src/Umbraco.Core/Packaging/PendingPackageMigrations.cs index 8485cc21bc..2635287db0 100644 --- a/src/Umbraco.Core/Packaging/PendingPackageMigrations.cs +++ b/src/Umbraco.Core/Packaging/PendingPackageMigrations.cs @@ -27,7 +27,7 @@ namespace Umbraco.Cms.Core.Packaging /// These are the key/value pairs from the keyvalue storage of migration names and their final values /// /// - public IReadOnlyList GetUmbracoPendingPackageMigrations(IReadOnlyDictionary keyValues) + public IReadOnlyList GetPendingPackageMigrations(IReadOnlyDictionary keyValues) { var packageMigrationPlans = _packageMigrationPlans.ToList(); diff --git a/src/Umbraco.Core/Services/IPackagingService.cs b/src/Umbraco.Core/Services/IPackagingService.cs index 4ab693147e..60f5c97408 100644 --- a/src/Umbraco.Core/Services/IPackagingService.cs +++ b/src/Umbraco.Core/Services/IPackagingService.cs @@ -24,10 +24,25 @@ namespace Umbraco.Cms.Core.Services InstallationSummary InstallCompiledPackageData(XDocument packageXml, int userId = Constants.Security.SuperUserId); - IEnumerable GetAllInstalledPackages(); + /// + /// Returns the advertised installed packages + /// + /// + IEnumerable GetAllInstalledPackages(); + /// + /// Returns the created packages + /// + /// IEnumerable GetAllCreatedPackages(); + + /// + /// Returns a created package by id + /// + /// + /// PackageDefinition GetCreatedPackageById(int id); + void DeleteCreatedPackage(int id, int userId = Constants.Security.SuperUserId); /// diff --git a/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs b/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs index 9a44121f0b..5b69e0374e 100644 --- a/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs +++ b/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs @@ -28,7 +28,7 @@ namespace Umbraco.Cms.Core.Manifest private readonly ILocalizedTextService _localizedTextService; private readonly IShortStringHelper _shortStringHelper; private readonly IDataValueEditorFactory _dataValueEditorFactory; - private static readonly string Utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); + private static readonly string s_utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); private readonly IAppPolicyCache _cache; private readonly ILogger _logger; @@ -97,7 +97,7 @@ namespace Umbraco.Cms.Core.Manifest /// /// Gets all manifests. /// - private IEnumerable GetManifests() + public IEnumerable GetManifests() { var manifests = new List(); @@ -108,8 +108,11 @@ namespace Umbraco.Cms.Core.Manifest var text = File.ReadAllText(path); text = TrimPreamble(text); if (string.IsNullOrWhiteSpace(text)) + { continue; - var manifest = ParseManifest(text); + } + + PackageManifest manifest = ParseManifest(text); manifest.Source = path; manifests.Add(manifest); } @@ -174,8 +177,8 @@ namespace Umbraco.Cms.Core.Manifest private static string TrimPreamble(string text) { // strangely StartsWith(preamble) would always return true - if (text.Substring(0, 1) == Utf8Preamble) - text = text.Remove(0, Utf8Preamble.Length); + if (text.Substring(0, 1) == s_utf8Preamble) + text = text.Remove(0, s_utf8Preamble.Length); return text; } diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs index 3ee6a2693d..b21ee2254d 100644 --- a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs @@ -212,7 +212,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime return UmbracoDatabaseState.NeedsUpgrade; } - IReadOnlyList packagesRequiringMigration = _packageMigrationState.GetUmbracoPendingPackageMigrations(keyValues); + IReadOnlyList packagesRequiringMigration = _packageMigrationState.GetPendingPackageMigrations(keyValues); if (packagesRequiringMigration.Count > 0) { _startupState[PendingPacakgeMigrationsStateKey] = packagesRequiringMigration; diff --git a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs index 93028af308..b8b1cc4dbf 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Xml.Linq; using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Packaging; using Umbraco.Cms.Core.Notifications; @@ -19,6 +20,10 @@ namespace Umbraco.Cms.Core.Services.Implement { private readonly IPackageInstallation _packageInstallation; private readonly IEventAggregator _eventAggregator; + private readonly IManifestParser _manifestParser; + private readonly IKeyValueService _keyValueService; + private readonly PackageMigrationPlanCollection _packageMigrationPlans; + private readonly PendingPackageMigrations _pendingPackageMigrations; private readonly IAuditService _auditService; private readonly ICreatedPackagesRepository _createdPackages; @@ -26,12 +31,20 @@ namespace Umbraco.Cms.Core.Services.Implement IAuditService auditService, ICreatedPackagesRepository createdPackages, IPackageInstallation packageInstallation, - IEventAggregator eventAggregator) + IEventAggregator eventAggregator, + IManifestParser manifestParser, + IKeyValueService keyValueService, + PackageMigrationPlanCollection packageMigrationPlans, + PendingPackageMigrations pendingPackageMigrations) { _auditService = auditService; _createdPackages = createdPackages; _packageInstallation = packageInstallation; _eventAggregator = eventAggregator; + _manifestParser = manifestParser; + _keyValueService = keyValueService; + _packageMigrationPlans = packageMigrationPlans; + _pendingPackageMigrations = pendingPackageMigrations; } #region Installation @@ -95,10 +108,52 @@ namespace Umbraco.Cms.Core.Services.Implement public string ExportCreatedPackage(PackageDefinition definition) => _createdPackages.ExportPackage(definition); + public IEnumerable GetAllInstalledPackages() + { + IReadOnlyDictionary keyValues = _keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix); + IReadOnlyList pendingMigrations = _pendingPackageMigrations.GetPendingPackageMigrations(keyValues); + + var installedPackages = new Dictionary(); + + foreach(PackageMigrationPlan plan in _packageMigrationPlans) + { + if (!installedPackages.TryGetValue(plan.PackageName, out InstalledPackage installedPackage)) + { + installedPackage = new InstalledPackage + { + PackageName = plan.PackageName + }; + installedPackages.Add(plan.PackageName, installedPackage); + } + + var currentPlans = installedPackage.PackageMigrationPlans.ToList(); + keyValues.TryGetValue(Constants.Conventions.Migrations.KeyValuePrefix + plan.PackageName, out var currentState); + currentPlans.Add(new InstalledPackageMigrationPlans + { + CurrentMigrationId = currentState, + FinalMigrationId = plan.FinalState + }); + + installedPackage.PackageMigrationPlans = currentPlans; + } + + foreach(PackageManifest package in _manifestParser.GetManifests()) + { + if (!installedPackages.TryGetValue(package.PackageName, out InstalledPackage installedPackage)) + { + installedPackage = new InstalledPackage + { + PackageName = package.PackageName + }; + installedPackages.Add(package.PackageName, installedPackage); + } + + installedPackage.PackageView = package.PackageView; + } + + return installedPackages.Values; + } - // TODO: Implement - public IEnumerable GetAllInstalledPackages() => Enumerable.Empty(); - #endregion } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Packaging/PendingPackageMigrationsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Packaging/PendingPackageMigrationsTests.cs index a9635b76ca..d726cf32f8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Packaging/PendingPackageMigrationsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Packaging/PendingPackageMigrationsTests.cs @@ -13,11 +13,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Packaging { private static readonly Guid s_step1 = Guid.NewGuid(); private static readonly Guid s_step2 = Guid.NewGuid(); - private const string PackageName = "Test1"; + private const string TestPackageName = "Test1"; private class TestPackageMigrationPlan : PackageMigrationPlan { - public TestPackageMigrationPlan() : base(PackageName) + public TestPackageMigrationPlan() : base(TestPackageName) { } @@ -41,7 +41,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Packaging { PendingPackageMigrations pendingPackageMigrations = GetPendingPackageMigrations(); var registeredMigrations = new Dictionary(); - IReadOnlyList pending = pendingPackageMigrations.GetUmbracoPendingPackageMigrations(registeredMigrations); + IReadOnlyList pending = pendingPackageMigrations.GetPendingPackageMigrations(registeredMigrations); Assert.AreEqual(1, pending.Count); } @@ -51,9 +51,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Packaging PendingPackageMigrations pendingPackageMigrations = GetPendingPackageMigrations(); var registeredMigrations = new Dictionary { - [Constants.Conventions.Migrations.KeyValuePrefix + PackageName] = s_step2.ToString() + [Constants.Conventions.Migrations.KeyValuePrefix + TestPackageName] = s_step2.ToString() }; - IReadOnlyList pending = pendingPackageMigrations.GetUmbracoPendingPackageMigrations(registeredMigrations); + IReadOnlyList pending = pendingPackageMigrations.GetPendingPackageMigrations(registeredMigrations); Assert.AreEqual(0, pending.Count); } @@ -63,9 +63,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Packaging PendingPackageMigrations pendingPackageMigrations = GetPendingPackageMigrations(); var registeredMigrations = new Dictionary { - [Constants.Conventions.Migrations.KeyValuePrefix + PackageName] = s_step1.ToString() + [Constants.Conventions.Migrations.KeyValuePrefix + TestPackageName] = s_step1.ToString() }; - IReadOnlyList pending = pendingPackageMigrations.GetUmbracoPendingPackageMigrations(registeredMigrations); + IReadOnlyList pending = pendingPackageMigrations.GetPendingPackageMigrations(registeredMigrations); Assert.AreEqual(1, pending.Count); } @@ -75,9 +75,9 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Packaging PendingPackageMigrations pendingPackageMigrations = GetPendingPackageMigrations(); var registeredMigrations = new Dictionary { - [Constants.Conventions.Migrations.KeyValuePrefix + PackageName] = s_step1.ToString().ToUpper() + [Constants.Conventions.Migrations.KeyValuePrefix + TestPackageName] = s_step1.ToString().ToUpper() }; - IReadOnlyList pending = pendingPackageMigrations.GetUmbracoPendingPackageMigrations(registeredMigrations); + IReadOnlyList pending = pendingPackageMigrations.GetPendingPackageMigrations(registeredMigrations); Assert.AreEqual(1, pending.Count); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs index 6395237ca9..60d4f5baad 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs @@ -129,10 +129,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// Returns all installed packages - only shows their latest versions /// /// - public IEnumerable GetInstalled() - { - return _packagingService.GetAllInstalledPackages() - .ToList(); - } + public IEnumerable GetInstalled() + => _packagingService.GetAllInstalledPackages().ToList(); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/packages/views/installed.controller.js b/src/Umbraco.Web.UI.Client/src/views/packages/views/installed.controller.js index 3496fca40c..8841bbf1eb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packages/views/installed.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packages/views/installed.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function PackagesInstalledController($scope, $route, $location, packageResource, $timeout, $window, localStorageService, localizationService) { + function PackagesInstalledController($location, packageResource, localizationService) { var vm = this; diff --git a/src/Umbraco.Web.UI.Client/src/views/packages/views/installed.html b/src/Umbraco.Web.UI.Client/src/views/packages/views/installed.html index 4091635e3f..fa39b08d97 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packages/views/installed.html +++ b/src/Umbraco.Web.UI.Client/src/views/packages/views/installed.html @@ -9,7 +9,7 @@ - + diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml index 942fac8ad4..e4b3bb95f4 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml @@ -1264,6 +1264,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
You can safely remove this from the system by clicking "uninstall package" below.]]>
Package options + Run pending package migrationsPackage readmePackage repositoryConfirm package uninstall diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml index 26a8c3ac03..d1a3ca634c 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml @@ -1276,6 +1276,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
You can safely remove this from the system by clicking "uninstall package" below.]]>
Package options + Run pending package migrationsPackage readmePackage repositoryConfirm package uninstall
@@ -17,25 +17,30 @@
{{ installedPackage.name }}
-
- {{ installedPackage.version }} | {{ installedPackage.url }} | {{ installedPackage.author }} -
- - + + - - + + + + +