AB3869 move packages to abstractions

This commit is contained in:
Bjarke Berg
2019-11-19 13:01:00 +01:00
parent b14c01e33d
commit 2c9a1d9735
14 changed files with 34 additions and 31 deletions

View File

@@ -1,196 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Packaging;
using File = System.IO.File;
namespace Umbraco.Core.Packaging
{
/// <summary>
/// Parses the xml document contained in a compiled (zip) Umbraco package
/// </summary>
internal class CompiledPackageXmlParser
{
private readonly ConflictingPackageData _conflictingPackageData;
public CompiledPackageXmlParser(ConflictingPackageData conflictingPackageData)
{
_conflictingPackageData = conflictingPackageData;
}
public CompiledPackage ToCompiledPackage(XDocument xml, FileInfo packageFile, string applicationRootFolder)
{
if (xml == null) throw new ArgumentNullException(nameof(xml));
if (xml.Root == null) throw new ArgumentException(nameof(xml), "The xml document is invalid");
if (xml.Root.Name != "umbPackage") throw new FormatException("The xml document is invalid");
var info = xml.Root.Element("info");
if (info == null) throw new FormatException("The xml document is invalid");
var package = info.Element("package");
if (package == null) throw new FormatException("The xml document is invalid");
var author = info.Element("author");
if (author == null) throw new FormatException("The xml document is invalid");
var requirements = package.Element("requirements");
if (requirements == null) throw new FormatException("The xml document is invalid");
var def = new CompiledPackage
{
PackageFile = packageFile,
Name = package.Element("name")?.Value,
Author = author.Element("name")?.Value,
AuthorUrl = author.Element("website")?.Value,
Contributors = info.Element("contributors")?.Elements("contributor").Select(x => x.Value).ToList() ?? new List<string>(),
Version = package.Element("version")?.Value,
Readme = info.Element("readme")?.Value,
License = package.Element("license")?.Value,
LicenseUrl = package.Element("license")?.AttributeValue<string>("url"),
Url = package.Element("url")?.Value,
IconUrl = package.Element("iconUrl")?.Value,
UmbracoVersion = new Version((int)requirements.Element("major"), (int)requirements.Element("minor"), (int)requirements.Element("patch")),
UmbracoVersionRequirementsType = requirements.AttributeValue<string>("type").IsNullOrWhiteSpace() ? RequirementsType.Legacy : Enum<RequirementsType>.Parse(requirements.AttributeValue<string>("type"), true),
PackageView = xml.Root.Element("view")?.Value,
Actions = xml.Root.Element("Actions")?.ToString(SaveOptions.None) ?? "<Actions></Actions>", //take the entire outer xml value
Files = xml.Root.Element("files")?.Elements("file")?.Select(CompiledPackageFile.Create).ToList() ?? new List<CompiledPackageFile>(),
Macros = xml.Root.Element("Macros")?.Elements("macro") ?? Enumerable.Empty<XElement>(),
Templates = xml.Root.Element("Templates")?.Elements("Template") ?? Enumerable.Empty<XElement>(),
Stylesheets = xml.Root.Element("Stylesheets")?.Elements("styleSheet") ?? Enumerable.Empty<XElement>(),
DataTypes = xml.Root.Element("DataTypes")?.Elements("DataType") ?? Enumerable.Empty<XElement>(),
Languages = xml.Root.Element("Languages")?.Elements("Language") ?? Enumerable.Empty<XElement>(),
DictionaryItems = xml.Root.Element("DictionaryItems")?.Elements("DictionaryItem") ?? Enumerable.Empty<XElement>(),
DocumentTypes = xml.Root.Element("DocumentTypes")?.Elements("DocumentType") ?? Enumerable.Empty<XElement>(),
Documents = xml.Root.Element("Documents")?.Elements("DocumentSet")?.Select(CompiledPackageDocument.Create) ?? Enumerable.Empty<CompiledPackageDocument>(),
};
def.Warnings = GetPreInstallWarnings(def, applicationRootFolder);
return def;
}
private PreInstallWarnings GetPreInstallWarnings(CompiledPackage package, string applicationRootFolder)
{
var sourceDestination = ExtractSourceDestinationFileInformation(package.Files);
var installWarnings = new PreInstallWarnings
{
ConflictingMacros = _conflictingPackageData.FindConflictingMacros(package.Macros),
ConflictingTemplates = _conflictingPackageData.FindConflictingTemplates(package.Templates),
ConflictingStylesheets = _conflictingPackageData.FindConflictingStylesheets(package.Stylesheets),
UnsecureFiles = FindUnsecureFiles(sourceDestination),
FilesReplaced = FindFilesToBeReplaced(sourceDestination, applicationRootFolder)
};
return installWarnings;
}
/// <summary>
/// Returns a tuple of the zip file's unique file name and it's application relative path
/// </summary>
/// <param name="packageFiles"></param>
/// <returns></returns>
public (string packageUniqueFile, string appRelativePath)[] ExtractSourceDestinationFileInformation(IEnumerable<CompiledPackageFile> packageFiles)
{
return packageFiles
.Select(e =>
{
var fileName = PrepareAsFilePathElement(e.OriginalName);
var relativeDir = UpdatePathPlaceholders(PrepareAsFilePathElement(e.OriginalPath));
var relativePath = Path.Combine(relativeDir, fileName);
return (e.UniqueFileName, relativePath);
}).ToArray();
}
private IEnumerable<string> FindFilesToBeReplaced(IEnumerable<(string packageUniqueFile, string appRelativePath)> sourceDestination, string applicationRootFolder)
{
return sourceDestination.Where(sd => File.Exists(Path.Combine(applicationRootFolder, sd.appRelativePath)))
.Select(x => x.appRelativePath)
.ToArray();
}
private IEnumerable<string> FindUnsecureFiles(IEnumerable<(string packageUniqueFile, string appRelativePath)> sourceDestinationPair)
{
return sourceDestinationPair.Where(sd => IsFileDestinationUnsecure(sd.appRelativePath))
.Select(x => x.appRelativePath)
.ToList();
}
private bool IsFileDestinationUnsecure(string destination)
{
var unsecureDirNames = new[] { "bin", "app_code" };
if (unsecureDirNames.Any(ud => destination.StartsWith(ud, StringComparison.InvariantCultureIgnoreCase)))
return true;
string extension = Path.GetExtension(destination);
return extension != null && extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase);
}
private static string PrepareAsFilePathElement(string pathElement)
{
return pathElement.TrimStart(new[] { '\\', '/', '~' }).Replace("/", "\\");
}
private static string UpdatePathPlaceholders(string path)
{
if (path.Contains("[$"))
{
//this is experimental and undocumented...
path = path.Replace("[$UMBRACO]", Current.Configs.Global().UmbracoPath);
path = path.Replace("[$CONFIG]", Constants.SystemDirectories.Config);
path = path.Replace("[$DATA]", Constants.SystemDirectories.Data);
}
return path;
}
/// <summary>
/// Parses the package actions stored in the package definition
/// </summary>
/// <param name="actionsElement"></param>
/// <param name="packageName"></param>
/// <returns></returns>
public static IEnumerable<PackageAction> GetPackageActions(XElement actionsElement, string packageName)
{
if (actionsElement == null) return Enumerable.Empty<PackageAction>();
//invariant check ... because people can really enter anything :/
if (!string.Equals("actions", actionsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase))
throw new FormatException("Must be \"<actions>\" as root");
if (!actionsElement.HasElements) return Enumerable.Empty<PackageAction>();
var actionElementName = actionsElement.Elements().First().Name.LocalName;
//invariant check ... because people can really enter anything :/
if (!string.Equals("action", actionElementName, StringComparison.InvariantCultureIgnoreCase))
throw new FormatException("Must be \"<action\" as element");
return actionsElement.Elements(actionElementName)
.Select(e =>
{
var aliasAttr = e.Attribute("alias") ?? e.Attribute("Alias"); //allow both ... because people can really enter anything :/
if (aliasAttr == null)
throw new ArgumentException("missing \"alias\" attribute in alias element", nameof(actionsElement));
var packageAction = new PackageAction
{
XmlData = e,
Alias = aliasAttr.Value,
PackageName = packageName,
};
var attr = e.Attribute("runat") ?? e.Attribute("Runat"); //allow both ... because people can really enter anything :/
if (attr != null && Enum.TryParse(attr.Value, true, out ActionRunAt runAt)) { packageAction.RunAt = runAt; }
attr = e.Attribute("undo") ?? e.Attribute("Undo"); //allow both ... because people can really enter anything :/
if (attr != null && bool.TryParse(attr.Value, out var undo)) { packageAction.Undo = undo; }
return packageAction;
}).ToArray();
}
}
}

View File

@@ -1,16 +0,0 @@
using Umbraco.Core.Models.Packaging;
namespace Umbraco.Core.Packaging
{
/// <summary>
/// Manages the storage of created package definitions
/// </summary>
public interface ICreatedPackagesRepository : IPackageDefinitionRepository
{
/// <summary>
/// Creates the package file and returns it's physical path
/// </summary>
/// <param name="definition"></param>
string ExportPackage(PackageDefinition definition);
}
}

View File

@@ -1,11 +0,0 @@
using System;
namespace Umbraco.Core.Packaging
{
/// <summary>
/// Manages the storage of installed package definitions
/// </summary>
public interface IInstalledPackagesRepository : IPackageDefinitionRepository
{
}
}

View File

@@ -1,23 +0,0 @@
using System.Collections.Generic;
using Umbraco.Core.Models.Packaging;
namespace Umbraco.Core.Packaging
{
/// <summary>
/// Defines methods for persisting package definitions to storage
/// </summary>
public interface IPackageDefinitionRepository
{
IEnumerable<PackageDefinition> GetAll();
PackageDefinition GetById(int id);
void Delete(int id);
/// <summary>
/// Persists a package definition to storage
/// </summary>
/// <returns>
/// true if creating/updating the package was successful, otherwise false
/// </returns>
bool SavePackage(PackageDefinition definition);
}
}

View File

@@ -1,43 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;
using Umbraco.Core.Models.Packaging;
namespace Umbraco.Core.Packaging
{
public interface IPackageInstallation
{
/// <summary>
/// This will run the uninstall sequence for this <see cref="PackageDefinition"/>
/// </summary>
/// <param name="packageDefinition"></param>
/// <param name="userId"></param>
/// <returns></returns>
UninstallationSummary UninstallPackage(PackageDefinition packageDefinition, int userId);
/// <summary>
/// Installs a packages data and entities
/// </summary>
/// <param name="packageDefinition"></param>
/// <param name="compiledPackage"></param>
/// <param name="userId"></param>
/// <returns></returns>
InstallationSummary InstallPackageData(PackageDefinition packageDefinition, CompiledPackage compiledPackage, int userId);
/// <summary>
/// Installs a packages files
/// </summary>
/// <param name="packageDefinition"></param>
/// <param name="compiledPackage"></param>
/// <param name="userId"></param>
/// <returns></returns>
IEnumerable<string> InstallPackageFiles(PackageDefinition packageDefinition, CompiledPackage compiledPackage, int userId);
/// <summary>
/// Reads the package (zip) file and returns the <see cref="CompiledPackage"/> model
/// </summary>
/// <param name="packageFile"></param>
/// <returns></returns>
CompiledPackage ReadPackage(FileInfo packageFile);
}
}

View File

@@ -6,13 +6,10 @@ using System.Web;
using System.Xml.Linq;
using System.Xml.XPath;
using Umbraco.Core.Collections;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Packaging;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;

View File

@@ -1,114 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Packaging;
namespace Umbraco.Core.Packaging
{
/// <summary>
/// Converts a <see cref="PackageDefinition"/> to and from XML
/// </summary>
public class PackageDefinitionXmlParser
{
private readonly ILogger _logger;
public PackageDefinitionXmlParser(ILogger logger)
{
_logger = logger;
}
public PackageDefinition ToPackageDefinition(XElement xml)
{
if (xml == null) return null;
var retVal = new PackageDefinition
{
Id = xml.AttributeValue<int>("id"),
Name = xml.AttributeValue<string>("name") ?? string.Empty,
PackagePath = xml.AttributeValue<string>("packagePath") ?? string.Empty,
Version = xml.AttributeValue<string>("version") ?? string.Empty,
Url = xml.AttributeValue<string>("url") ?? string.Empty,
PackageId = xml.AttributeValue<Guid>("packageGuid"),
IconUrl = xml.AttributeValue<string>("iconUrl") ?? string.Empty,
UmbracoVersion = xml.AttributeValue<Version>("umbVersion"),
PackageView = xml.AttributeValue<string>("view") ?? string.Empty,
License = xml.Element("license")?.Value ?? string.Empty,
LicenseUrl = xml.Element("license")?.AttributeValue<string>("url") ?? string.Empty,
Author = xml.Element("author")?.Value ?? string.Empty,
AuthorUrl = xml.Element("author")?.AttributeValue<string>("url") ?? string.Empty,
Contributors = xml.Element("contributors")?.Elements("contributor").Select(x => x.Value).ToList() ?? new List<string>(),
Readme = xml.Element("readme")?.Value ?? string.Empty,
Actions = xml.Element("actions")?.ToString(SaveOptions.None) ?? "<actions></actions>", //take the entire outer xml value
ContentNodeId = xml.Element("content")?.AttributeValue<string>("nodeId") ?? string.Empty,
ContentLoadChildNodes = xml.Element("content")?.AttributeValue<bool>("loadChildNodes") ?? false,
Macros = xml.Element("macros")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
Templates = xml.Element("templates")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
Stylesheets = xml.Element("stylesheets")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
DocumentTypes = xml.Element("documentTypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
Languages = xml.Element("languages")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
DictionaryItems = xml.Element("dictionaryitems")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
DataTypes = xml.Element("datatypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
Files = xml.Element("files")?.Elements("file").Select(x => x.Value).ToList() ?? new List<string>()
};
return retVal;
}
public XElement ToXml(PackageDefinition def)
{
var actionsXml = new XElement("actions");
try
{
actionsXml = XElement.Parse(def.Actions);
}
catch (Exception e)
{
_logger.Warn<PackagesRepository>(e, "Could not add package actions to the package xml definition, the xml did not parse");
}
var packageXml = new XElement("package",
new XAttribute("id", def.Id),
new XAttribute("version", def.Version ?? string.Empty),
new XAttribute("url", def.Url ?? string.Empty),
new XAttribute("name", def.Name ?? string.Empty),
new XAttribute("packagePath", def.PackagePath ?? string.Empty),
new XAttribute("iconUrl", def.IconUrl ?? string.Empty),
new XAttribute("umbVersion", def.UmbracoVersion),
new XAttribute("packageGuid", def.PackageId),
new XAttribute("view", def.PackageView ?? string.Empty),
new XElement("license",
new XCData(def.License ?? string.Empty),
new XAttribute("url", def.LicenseUrl ?? string.Empty)),
new XElement("author",
new XCData(def.Author ?? string.Empty),
new XAttribute("url", def.AuthorUrl ?? string.Empty)),
new XElement("contributors", (def.Contributors ?? Array.Empty<string>()).Where(x => !x.IsNullOrWhiteSpace()).Select(x => new XElement("contributor", x))),
new XElement("readme", new XCData(def.Readme ?? string.Empty)),
actionsXml,
new XElement("datatypes", string.Join(",", def.DataTypes ?? Array.Empty<string>())),
new XElement("content",
new XAttribute("nodeId", def.ContentNodeId ?? string.Empty),
new XAttribute("loadChildNodes", def.ContentLoadChildNodes)),
new XElement("templates", string.Join(",", def.Templates ?? Array.Empty<string>())),
new XElement("stylesheets", string.Join(",", def.Stylesheets ?? Array.Empty<string>())),
new XElement("documentTypes", string.Join(",", def.DocumentTypes ?? Array.Empty<string>())),
new XElement("macros", string.Join(",", def.Macros ?? Array.Empty<string>())),
new XElement("files", (def.Files ?? Array.Empty<string>()).Where(x => !x.IsNullOrWhiteSpace()).Select(x => new XElement("file", x))),
new XElement("languages", string.Join(",", def.Languages ?? Array.Empty<string>())),
new XElement("dictionaryitems", string.Join(",", def.DictionaryItems ?? Array.Empty<string>())));
return packageXml;
}
}
}

View File

@@ -1,92 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Packaging;
using Umbraco.Core.Services;
using File = System.IO.File;
namespace Umbraco.Core.Packaging
{
/// <summary>
/// Installs package files
/// </summary>
internal class PackageFileInstallation
{
private readonly CompiledPackageXmlParser _parser;
private readonly IIOHelper _ioHelper;
private readonly IProfilingLogger _logger;
private readonly PackageExtraction _packageExtraction;
public PackageFileInstallation(CompiledPackageXmlParser parser, IIOHelper ioHelper, IProfilingLogger logger)
{
_parser = parser;
_ioHelper = ioHelper;
_logger = logger;
_packageExtraction = new PackageExtraction();
}
/// <summary>
/// Returns a list of all installed file paths
/// </summary>
/// <param name="compiledPackage"></param>
/// <param name="packageFile"></param>
/// <param name="targetRootFolder">
/// The absolute path of where to extract the package files (normally the application root)
/// </param>
/// <returns></returns>
public IEnumerable<string> InstallFiles(CompiledPackage compiledPackage, FileInfo packageFile, string targetRootFolder)
{
using (_logger.DebugDuration<PackageFileInstallation>(
"Installing package files for package " + compiledPackage.Name,
"Package file installation complete for package " + compiledPackage.Name))
{
var sourceAndRelativeDest = _parser.ExtractSourceDestinationFileInformation(compiledPackage.Files);
var sourceAndAbsDest = AppendRootToDestination(targetRootFolder, sourceAndRelativeDest);
_packageExtraction.CopyFilesFromArchive(packageFile, sourceAndAbsDest);
return sourceAndRelativeDest.Select(sd => sd.appRelativePath).ToArray();
}
}
public IEnumerable<string> UninstallFiles(PackageDefinition package)
{
var removedFiles = new List<string>();
foreach (var item in package.Files.ToArray())
{
removedFiles.Add(_ioHelper.GetRelativePath(item));
//here we need to try to find the file in question as most packages does not support the tilde char
var file = _ioHelper.FindFile(item);
if (file != null)
{
// TODO: Surely this should be ~/ ?
file = file.EnsureStartsWith("/");
var filePath = _ioHelper.MapPath(file);
if (File.Exists(filePath))
File.Delete(filePath);
}
package.Files.Remove(file);
}
return removedFiles;
}
private static IEnumerable<(string packageUniqueFile, string appAbsolutePath)> AppendRootToDestination(string applicationRootFolder, IEnumerable<(string packageUniqueFile, string appRelativePath)> sourceDestination)
{
return
sourceDestination.Select(
sd => (sd.packageUniqueFile, Path.Combine(applicationRootFolder, sd.appRelativePath))).ToArray();
}
}
}