Gets package upgrading working along with package uninstallation with multiple versions

This commit is contained in:
Shannon
2019-01-16 17:25:46 +11:00
parent 90be93d948
commit 94da9a6681
10 changed files with 110 additions and 131 deletions

View File

@@ -3,16 +3,13 @@ using Umbraco.Core.Models.Packaging;
namespace Umbraco.Core.Events
{
public class UninstallPackageEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>
public class UninstallPackageEventArgs: CancellableObjectEventArgs<IEnumerable<UninstallationSummary>>
{
public UninstallPackageEventArgs(TEntity eventObject, IPackageInfo packageMetaData, bool canCancel)
: base(new[] { eventObject }, canCancel)
public UninstallPackageEventArgs(IEnumerable<UninstallationSummary> eventObject, bool canCancel)
: base(eventObject, canCancel)
{
PackageMetaData = packageMetaData;
}
public IPackageInfo PackageMetaData { get; }
public IEnumerable<TEntity> UninstallationSummary => EventObject;
public IEnumerable<UninstallationSummary> UninstallationSummary => EventObject;
}
}

View File

@@ -8,7 +8,7 @@ namespace Umbraco.Core.Packaging
public interface IPackageInstallation
{
/// <summary>
/// Uninstalls a package including all data, entities and files
/// This will run the uninstallation sequence for this <see cref="PackageDefinition"/>
/// </summary>
/// <param name="packageDefinition"></param>
/// <param name="userId"></param>

View File

@@ -228,14 +228,17 @@ namespace Umbraco.Core.Packaging
}
var content = CreateContentFromXml(root, importedContentTypes[contentTypeAlias], null, parentId);
if (content == null) continue;
contents.Add(content);
var children = (from child in root.Elements()
where (string)child.Attribute("isDoc") == ""
select child)
.ToList();
if (children.Any())
contents.AddRange(CreateContentFromXml(children, content, importedContentTypes));
if (children.Count > 0)
contents.AddRange(CreateContentFromXml(children, content, importedContentTypes).WhereNotNull());
}
return contents;
}
@@ -258,7 +261,7 @@ namespace Umbraco.Core.Packaging
list.Add(content);
//Recursive call
XElement child1 = child;
var child1 = child;
var grandChildren = (from grand in child1.Elements()
where (string)grand.Attribute("isDoc") == ""
select grand).ToList();
@@ -272,37 +275,43 @@ namespace Umbraco.Core.Packaging
private IContent CreateContentFromXml(XElement element, IContentType contentType, IContent parent, int parentId)
{
var key = Guid.Empty;
if (element.Attribute("key") != null && Guid.TryParse(element.Attribute("key").Value, out key))
{
//if a Key is supplied, then we need to check if the content already exists and if so we ignore the installation for this item
if (_contentService.GetById(key) != null)
return null;
}
var id = element.Attribute("id").Value;
var level = element.Attribute("level").Value;
var sortOrder = element.Attribute("sortOrder").Value;
var nodeName = element.Attribute("nodeName").Value;
var path = element.Attribute("path").Value;
//TODO: Shouldn't we be using this value???
var template = element.Attribute("template").Value;
var key = Guid.Empty;
var templateId = element.AttributeValue<int?>("template");
var properties = from property in element.Elements()
where property.Attribute("isDoc") == null
select property;
var template = templateId.HasValue ? _fileService.GetTemplate(templateId.Value) : null;
IContent content = parent == null
? new Content(nodeName, parentId, contentType)
{
Level = int.Parse(level),
SortOrder = int.Parse(sortOrder)
SortOrder = int.Parse(sortOrder),
TemplateId = template?.Id,
Key = key
}
: new Content(nodeName, parent, contentType)
{
Level = int.Parse(level),
SortOrder = int.Parse(sortOrder)
SortOrder = int.Parse(sortOrder),
TemplateId = template?.Id,
Key = key
};
if (element.Attribute("key") != null && Guid.TryParse(element.Attribute("key").Value, out key))
{
// update the Guid (for UDI support)
content.Key = key;
}
foreach (var property in properties)
{
string propertyTypeAlias = property.Name.LocalName;

View File

@@ -76,12 +76,13 @@ namespace Umbraco.Core.Packaging
return files;
}
/// <inheritdoc />
public UninstallationSummary UninstallPackage(PackageDefinition package, int userId)
{
//running this will update the PackageDefinition with the items being removed
var summary = _packageDataInstallation.UninstallPackageData(package, userId);
summary.Actions = _parser.GetPackageActions(XElement.Parse(package.Actions), package.Name);
summary.Actions = CompiledPackageXmlParser.GetPackageActions(XElement.Parse(package.Actions), package.Name);
//run actions before files are removed
summary.ActionErrors = UndoPackageActions(package, summary.Actions).ToList();
@@ -109,7 +110,7 @@ namespace Umbraco.Core.Packaging
installationSummary.StylesheetsInstalled = _packageDataInstallation.ImportStylesheets(compiledPackage.Stylesheets, userId);
installationSummary.ContentInstalled = _packageDataInstallation.ImportContent(compiledPackage.Documents, importedDocTypes, userId);
installationSummary.Actions = _parser.GetPackageActions(XElement.Parse(compiledPackage.Actions), compiledPackage.Name);
installationSummary.Actions = CompiledPackageXmlParser.GetPackageActions(XElement.Parse(compiledPackage.Actions), compiledPackage.Name);
installationSummary.MetaData = compiledPackage;
installationSummary.FilesInstalled = packageDefinition.Files;

View File

@@ -120,11 +120,6 @@ namespace Umbraco.Core.Packaging
if (definition.Id == default)
{
//check if the name already exists
var existsByName = packagesXml.Root.Elements("package").Any(x => x.AttributeValue<string>("name") == definition.Name);
if (existsByName)
return false;
//need to gen an id and persist
// Find max id
var maxId = packagesXml.Root.Elements("package").Max(x => x.AttributeValue<int?>("id")) ?? 0;

View File

@@ -37,15 +37,35 @@ namespace Umbraco.Core.Services
/// <param name="userId"></param>
InstallationSummary InstallCompiledPackageData(PackageDefinition packageDefinition, FileInfo packageFile, int userId = 0);
UninstallationSummary UninstallPackage(PackageDefinition packageDefinition, int userId = 0);
/// <summary>
/// Uninstalls all versions of the package by name
/// </summary>
/// <param name="packageName"></param>
/// <param name="userId"></param>
/// <returns></returns>
UninstallationSummary UninstallPackage(string packageName, int userId = 0);
#endregion
#region Installed Packages
IEnumerable<PackageDefinition> GetAllInstalledPackages();
/// <summary>
/// Returns the <see cref="PackageDefinition"/> for the installation id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
PackageDefinition GetInstalledPackageById(int id);
PackageDefinition GetInstalledPackageByName(string name);
/// <summary>
/// Returns all <see cref="PackageDefinition"/> for the package by name
/// </summary>
/// <param name="name"></param>
/// <returns>
/// A list of all package definitions installed for this package (i.e. original install and any upgrades)
/// </returns>
IEnumerable<PackageDefinition> GetInstalledPackageByName(string name);
/// <summary>
/// Returns a <see cref="PackageInstallType"/> for a given package name and version
@@ -57,14 +77,6 @@ namespace Umbraco.Core.Services
PackageInstallType GetPackageInstallType(string packageName, SemVersion packageVersion, out PackageDefinition alreadyInstalled);
void DeleteInstalledPackage(int packageId, int userId = 0);
/// <summary>
/// Merges the package definition information from the upgrade on to the original and returns the merged definition
/// </summary>
/// <param name="original"></param>
/// <param name="upgrade"></param>
/// <returns></returns>
PackageDefinition MergePackageDefinition(PackageDefinition original, PackageDefinition upgrade);
/// <summary>
/// Persists a package definition to storage
/// </summary>

View File

@@ -44,7 +44,7 @@ namespace Umbraco.Core.Services.Implement
ICreatedPackagesRepository createdPackages,
IInstalledPackagesRepository installedPackages,
IPackageInstallation packageInstallation)
{
{
_auditService = auditService;
_createdPackages = createdPackages;
_installedPackages = installedPackages;
@@ -110,7 +110,7 @@ namespace Umbraco.Core.Services.Implement
if (compiledPackage == null) throw new InvalidOperationException("Could not read the package file " + packageFile);
var files = _packageInstallation.InstallPackageFiles(packageDefinition, compiledPackage, userId).ToList();
SaveInstalledPackage(packageDefinition);
_auditService.Add(AuditType.PackagerInstall, userId, -1, "Package", $"Package files installed for package '{compiledPackage.Name}'.");
@@ -141,16 +141,44 @@ namespace Umbraco.Core.Services.Implement
return summary;
}
public UninstallationSummary UninstallPackage(PackageDefinition package, int userId = 0)
public UninstallationSummary UninstallPackage(string packageName, int userId = 0)
{
var summary = _packageInstallation.UninstallPackage(package, userId);
SaveInstalledPackage(package);
//this is ordered by descending version
var allPackageVersions = GetInstalledPackageByName(packageName)?.ToList();
if (allPackageVersions == null || allPackageVersions.Count == 0)
throw new InvalidOperationException("No installed package found by name " + packageName);
DeleteInstalledPackage(package.Id, userId);
var summary = new UninstallationSummary
{
MetaData = allPackageVersions[0]
};
var allSummaries = new List<UninstallationSummary>();
foreach (var packageVersion in allPackageVersions)
{
var versionUninstallSummary = _packageInstallation.UninstallPackage(packageVersion, userId);
allSummaries.Add(versionUninstallSummary);
//merge the summary
summary.ActionErrors = summary.ActionErrors.Concat(versionUninstallSummary.ActionErrors).Distinct().ToList();
summary.Actions = summary.Actions.Concat(versionUninstallSummary.Actions).Distinct().ToList();
summary.DataTypesUninstalled = summary.DataTypesUninstalled.Concat(versionUninstallSummary.DataTypesUninstalled).Distinct().ToList();
summary.DictionaryItemsUninstalled = summary.DictionaryItemsUninstalled.Concat(versionUninstallSummary.DictionaryItemsUninstalled).Distinct().ToList();
summary.DocumentTypesUninstalled = summary.DocumentTypesUninstalled.Concat(versionUninstallSummary.DocumentTypesUninstalled).Distinct().ToList();
summary.FilesUninstalled = summary.FilesUninstalled.Concat(versionUninstallSummary.FilesUninstalled).Distinct().ToList();
summary.LanguagesUninstalled = summary.LanguagesUninstalled.Concat(versionUninstallSummary.LanguagesUninstalled).Distinct().ToList();
summary.MacrosUninstalled = summary.MacrosUninstalled.Concat(versionUninstallSummary.MacrosUninstalled).Distinct().ToList();
summary.StylesheetsUninstalled = summary.StylesheetsUninstalled.Concat(versionUninstallSummary.StylesheetsUninstalled).Distinct().ToList();
summary.TemplatesUninstalled = summary.TemplatesUninstalled.Concat(versionUninstallSummary.TemplatesUninstalled).Distinct().ToList();
SaveInstalledPackage(packageVersion);
DeleteInstalledPackage(packageVersion.Id, userId);
}
// trigger the UninstalledPackage event
UninstalledPackage.RaiseEvent(new UninstallPackageEventArgs<UninstallationSummary>(summary, package, false), this);
UninstalledPackage.RaiseEvent(new UninstallPackageEventArgs(allSummaries, false), this);
return summary;
}
@@ -181,9 +209,9 @@ namespace Umbraco.Core.Services.Implement
public PackageDefinition GetInstalledPackageById(int id) => _installedPackages.GetById(id);
public PackageDefinition GetInstalledPackageByName(string name)
public IEnumerable<PackageDefinition> GetInstalledPackageByName(string name)
{
var found = _installedPackages.GetAll().FirstOrDefault(x => x.Name.InvariantEquals(name));
var found = _installedPackages.GetAll().Where(x => x.Name.InvariantEquals(name)).OrderByDescending(x => SemVersion.Parse(x.Version));
return found;
}
@@ -192,7 +220,8 @@ namespace Umbraco.Core.Services.Implement
if (packageName == null) throw new ArgumentNullException(nameof(packageName));
if (packageVersion == null) throw new ArgumentNullException(nameof(packageVersion));
alreadyInstalled = GetInstalledPackageByName(packageName);
//get the latest version installed
alreadyInstalled = GetInstalledPackageByName(packageName)?.OrderByDescending(x => SemVersion.Parse(x.Version)).FirstOrDefault();
if (alreadyInstalled == null) return PackageInstallType.NewInstall;
if (!SemVersion.TryParse(alreadyInstalled.Version, out var installedVersion))
@@ -205,62 +234,6 @@ namespace Umbraco.Core.Services.Implement
return PackageInstallType.Upgrade;
}
public PackageDefinition MergePackageDefinition(PackageDefinition original, PackageDefinition upgrade)
{
if (!SemVersion.TryParse(original.Version, out var originalVersion))
throw new InvalidOperationException("Could not parse the original version");
if(!SemVersion.TryParse(upgrade.Version, out var upgradeVersion))
throw new InvalidOperationException("Could not parse the upgrade version");
if (originalVersion >= upgradeVersion)
throw new InvalidOperationException("The upgrade version must be higher than the original version");
if (!original.Name.InvariantEquals(upgrade.Name))
throw new InvalidOperationException("Cannot merge the package definitions, the package name doesn't match");
var result = original.Clone();
result.PackagePath = upgrade.PackagePath;
result.PackageId = upgrade.PackageId;
result.UmbracoVersion = upgrade.UmbracoVersion;
result.Version = upgrade.Version;
result.Url = upgrade.Url;
result.Readme = upgrade.Readme;
result.AuthorUrl = upgrade.AuthorUrl;
result.Author = upgrade.Author;
result.LicenseUrl = upgrade.LicenseUrl;
result.PackageId = upgrade.PackageId;
result.Control = upgrade.Control;
result.IconUrl = upgrade.IconUrl;
result.License = upgrade.License;
result.ContentNodeId = upgrade.ContentNodeId;
result.ContentLoadChildNodes = upgrade.ContentLoadChildNodes;
result.Files = original.Files.Concat(upgrade.Files).Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();
result.DataTypes = original.DataTypes.Concat(upgrade.DataTypes).Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();
result.Templates = original.Templates.Concat(upgrade.Templates).Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();
result.Languages = original.Languages.Concat(upgrade.Languages).Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();
result.Macros = original.Macros.Concat(upgrade.Macros).Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();
result.Stylesheets = original.Stylesheets.Concat(upgrade.Stylesheets).Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();
result.DocumentTypes = original.DocumentTypes.Concat(upgrade.DocumentTypes).Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();
result.DictionaryItems = original.DictionaryItems.Concat(upgrade.DictionaryItems).Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();
var originalActions = CompiledPackageXmlParser.GetPackageActions(XElement.Parse(result.Actions), result.Name);
var upgradeActions = CompiledPackageXmlParser.GetPackageActions(XElement.Parse(upgrade.Actions), result.Name).ToList();
var upgradeActionsLookup = upgradeActions.ToLookup(x => x.Alias, x => x.XmlData.ToString());
foreach (var originalAction in originalActions)
{
var upgradeActionsWithAlias = upgradeActionsLookup[originalAction.Alias];
//check if the original action does not exist already in our upgrade actions
if(upgradeActionsWithAlias.All(upgradeActionXml => upgradeActionXml != originalAction.XmlData.ToString()))
upgradeActions.Add(originalAction);
}
result.Actions = new XElement("actions", upgradeActions.Select(x => x.XmlData)).ToString();
return result;
}
public bool SaveInstalledPackage(PackageDefinition definition) => _installedPackages.SavePackage(definition);
public void DeleteInstalledPackage(int packageId, int userId = 0)
@@ -289,10 +262,10 @@ namespace Umbraco.Core.Services.Implement
/// <summary>
/// Occurs after a package is uninstalled
/// </summary>
public static event TypedEventHandler<IPackagingService, UninstallPackageEventArgs<UninstallationSummary>> UninstalledPackage;
public static event TypedEventHandler<IPackagingService, UninstallPackageEventArgs> UninstalledPackage;
#endregion
}
}

View File

@@ -133,7 +133,7 @@
</umb-control-group>
<umb-control-group label="Document Types">
<div ng-repeat="doctype in vm.documentTypes">
<div ng-repeat="doctype in ::vm.documentTypes | orderBy:'name'">
<label>
<input
type="checkbox"
@@ -146,7 +146,7 @@
</umb-control-group>
<umb-control-group label="Templates">
<div ng-repeat="template in vm.templates">
<div ng-repeat="template in ::vm.templates | orderBy:'name'">
<label>
<input
type="checkbox"
@@ -158,7 +158,7 @@
</umb-control-group>
<umb-control-group label="Stylesheets">
<div ng-repeat="stylesheet in vm.stylesheets">
<div ng-repeat="stylesheet in ::vm.stylesheets | orderBy:'name'">
<label>
<input
type="checkbox"
@@ -170,7 +170,7 @@
</umb-control-group>
<umb-control-group label="Macros">
<div ng-repeat="macro in vm.macros">
<div ng-repeat="macro in ::vm.macros | orderBy:'name'">
<label>
<input type="checkbox"
checklist-model="vm.package.macros"
@@ -182,7 +182,7 @@
</umb-control-group>
<umb-control-group label="Languages">
<div ng-repeat="language in vm.languages">
<div ng-repeat="language in ::vm.languages | orderBy:'name'">
<label>
<input
type="checkbox"
@@ -194,7 +194,7 @@
</umb-control-group>
<umb-control-group label="Dictionary Items">
<div ng-repeat="dictionaryItem in vm.dictionaryItems">
<div ng-repeat="dictionaryItem in ::vm.dictionaryItems | orderBy:'name'">
<label>
<input
type="checkbox"
@@ -206,7 +206,7 @@
</umb-control-group>
<umb-control-group label="Data Types">
<div ng-repeat="dataType in vm.dataTypes">
<div ng-repeat="dataType in ::vm.dataTypes | orderBy:'name'">
<label>
<input
type="checkbox"

View File

@@ -6,6 +6,7 @@ using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web.Http;
using Semver;
using Umbraco.Core.IO;
using Umbraco.Core.Models.Packaging;
using Umbraco.Web.Models.ContentEditing;
@@ -125,9 +126,9 @@ namespace Umbraco.Web.Editors
//group by name
x => x.Name,
//select the package with a parsed version
pck => Version.TryParse(pck.Version, out var pckVersion)
pck => SemVersion.TryParse(pck.Version, out var pckVersion)
? new { package = pck, version = pckVersion }
: new { package = pck, version = new Version(0, 0, 0) })
: new { package = pck, version = new SemVersion(0, 0, 0) })
.Select(grouping =>
{
//get the max version for the package

View File

@@ -76,7 +76,7 @@ namespace Umbraco.Web.Editors
var package = Services.PackagingService.GetInstalledPackageById(packageId);
if (package == null) return NotFound();
var summary = Services.PackagingService.UninstallPackage(package);
var summary = Services.PackagingService.UninstallPackage(package.Name, Security.GetUserId().ResultOr(0));
//now get all other packages by this name since we'll uninstall all versions
foreach (var installed in Services.PackagingService.GetAllInstalledPackages()
@@ -283,22 +283,13 @@ namespace Umbraco.Web.Editors
case PackageInstallType.AlreadyInstalled:
throw new InvalidOperationException("The package is already installed");
case PackageInstallType.NewInstall:
//save to the installedPackages.config
case PackageInstallType.Upgrade:
//save to the installedPackages.config, this will create a new entry with a new Id
if (!Services.PackagingService.SaveInstalledPackage(packageDefinition))
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Could not save the package"));
model.Id = packageDefinition.Id;
break;
case PackageInstallType.Upgrade:
//we need to append any changes from the new packageDefinition to the alreadyInstalled definition
var mergedDefinition = Services.PackagingService.MergePackageDefinition(alreadyInstalled, packageDefinition);
//update the installedPackages.config with the upgrade definition
if (!Services.PackagingService.SaveInstalledPackage(packageDefinition))
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("Could not save the package"));
break;
default:
throw new ArgumentOutOfRangeException();