196 lines
9.4 KiB
C#
196 lines
9.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Xml.Linq;
|
|
using Umbraco.Core.IO;
|
|
using Umbraco.Core.Models;
|
|
using Umbraco.Core.Models.Packaging;
|
|
using Umbraco.Core.Services;
|
|
|
|
namespace Umbraco.Core.Packaging
|
|
{
|
|
internal class PackageInstallation : IPackageInstallation
|
|
{
|
|
private readonly PackageExtraction _packageExtraction;
|
|
private readonly PackageDataInstallation _packageDataInstallation;
|
|
private readonly PackageFileInstallation _packageFileInstallation;
|
|
private readonly CompiledPackageXmlParser _parser;
|
|
private readonly IPackageActionRunner _packageActionRunner;
|
|
private readonly DirectoryInfo _applicationRootFolder;
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="packageDataInstallation"></param>
|
|
/// <param name="packageFileInstallation"></param>
|
|
/// <param name="parser"></param>
|
|
/// <param name="packageActionRunner"></param>
|
|
/// <param name="applicationRootFolder">
|
|
/// The root folder of the application
|
|
/// </param>
|
|
public PackageInstallation(PackageDataInstallation packageDataInstallation, PackageFileInstallation packageFileInstallation, CompiledPackageXmlParser parser, IPackageActionRunner packageActionRunner,
|
|
DirectoryInfo applicationRootFolder)
|
|
{
|
|
_packageExtraction = new PackageExtraction();
|
|
_packageFileInstallation = packageFileInstallation ?? throw new ArgumentNullException(nameof(packageFileInstallation));
|
|
_packageDataInstallation = packageDataInstallation ?? throw new ArgumentNullException(nameof(packageDataInstallation));
|
|
_parser = parser ?? throw new ArgumentNullException(nameof(parser));
|
|
_packageActionRunner = packageActionRunner ?? throw new ArgumentNullException(nameof(packageActionRunner));
|
|
_applicationRootFolder = applicationRootFolder ?? throw new ArgumentNullException(nameof(applicationRootFolder));
|
|
}
|
|
|
|
public CompiledPackage ReadPackage(FileInfo packageFile)
|
|
{
|
|
if (packageFile == null) throw new ArgumentNullException(nameof(packageFile));
|
|
var doc = GetConfigXmlDoc(packageFile);
|
|
|
|
var compiledPackage = _parser.ToCompiledPackage(doc, packageFile, _applicationRootFolder.FullName);
|
|
|
|
ValidatePackageFile(packageFile, compiledPackage);
|
|
|
|
return compiledPackage;
|
|
}
|
|
|
|
public IEnumerable<string> InstallPackageFiles(PackageDefinition packageDefinition, CompiledPackage compiledPackage, int userId)
|
|
{
|
|
if (packageDefinition == null) throw new ArgumentNullException(nameof(packageDefinition));
|
|
if (compiledPackage == null) throw new ArgumentNullException(nameof(compiledPackage));
|
|
|
|
//these should be the same, TODO: we should have a better validator for this
|
|
if (packageDefinition.Name != compiledPackage.Name)
|
|
throw new InvalidOperationException("The package definition does not match the compiled package manifest");
|
|
|
|
var packageZipFile = compiledPackage.PackageFile;
|
|
|
|
var files = _packageFileInstallation.InstallFiles(compiledPackage, packageZipFile, _applicationRootFolder.FullName).ToList();
|
|
|
|
packageDefinition.Files = files;
|
|
|
|
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 = CompiledPackageXmlParser.GetPackageActions(XElement.Parse(package.Actions), package.Name);
|
|
|
|
//run actions before files are removed
|
|
summary.ActionErrors = UndoPackageActions(package, summary.Actions).ToList();
|
|
|
|
var filesRemoved = _packageFileInstallation.UninstallFiles(package);
|
|
summary.FilesUninstalled = filesRemoved;
|
|
|
|
return summary;
|
|
}
|
|
|
|
public InstallationSummary InstallPackageData(PackageDefinition packageDefinition, CompiledPackage compiledPackage, int userId)
|
|
{
|
|
var installationSummary = _packageDataInstallation.InstallPackageData(compiledPackage, userId);
|
|
|
|
installationSummary.Actions = CompiledPackageXmlParser.GetPackageActions(XElement.Parse(compiledPackage.Actions), compiledPackage.Name);
|
|
installationSummary.MetaData = compiledPackage;
|
|
installationSummary.FilesInstalled = packageDefinition.Files;
|
|
|
|
//make sure the definition is up to date with everything
|
|
foreach (var x in installationSummary.DataTypesInstalled) packageDefinition.DataTypes.Add(x.Id.ToInvariantString());
|
|
foreach (var x in installationSummary.LanguagesInstalled) packageDefinition.Languages.Add(x.Id.ToInvariantString());
|
|
foreach (var x in installationSummary.DictionaryItemsInstalled) packageDefinition.DictionaryItems.Add(x.Id.ToInvariantString());
|
|
foreach (var x in installationSummary.MacrosInstalled) packageDefinition.Macros.Add(x.Id.ToInvariantString());
|
|
foreach (var x in installationSummary.TemplatesInstalled) packageDefinition.Templates.Add(x.Id.ToInvariantString());
|
|
foreach (var x in installationSummary.DocumentTypesInstalled) packageDefinition.DocumentTypes.Add(x.Id.ToInvariantString());
|
|
foreach (var x in installationSummary.StylesheetsInstalled) packageDefinition.Stylesheets.Add(x.Id.ToInvariantString());
|
|
var contentInstalled = installationSummary.ContentInstalled.ToList();
|
|
packageDefinition.ContentNodeId = contentInstalled.Count > 0 ? contentInstalled[0].Id.ToInvariantString() : null;
|
|
|
|
//run package actions
|
|
installationSummary.ActionErrors = RunPackageActions(packageDefinition, installationSummary.Actions).ToList();
|
|
|
|
return installationSummary;
|
|
}
|
|
|
|
private IEnumerable<string> RunPackageActions(PackageDefinition packageDefinition, IEnumerable<PackageAction> actions)
|
|
{
|
|
foreach (var n in actions)
|
|
{
|
|
//if there is an undo section then save it to the definition so we can run it at uninstallation
|
|
var undo = n.Undo;
|
|
if (undo)
|
|
packageDefinition.Actions += n.XmlData.ToString();
|
|
|
|
//Run the actions tagged only for 'install'
|
|
if (n.RunAt != ActionRunAt.Install) continue;
|
|
|
|
if (n.Alias.IsNullOrWhiteSpace()) continue;
|
|
|
|
//run the actions and report errors
|
|
if (!_packageActionRunner.RunPackageAction(packageDefinition.Name, n.Alias, n.XmlData, out var err))
|
|
foreach (var e in err) yield return e;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<string> UndoPackageActions(IPackageInfo packageDefinition, IEnumerable<PackageAction> actions)
|
|
{
|
|
foreach (var n in actions)
|
|
{
|
|
//Run the actions tagged only for 'uninstall'
|
|
if (n.RunAt != ActionRunAt.Uninstall) continue;
|
|
|
|
if (n.Alias.IsNullOrWhiteSpace()) continue;
|
|
|
|
//run the actions and report errors
|
|
if (!_packageActionRunner.UndoPackageAction(packageDefinition.Name, n.Alias, n.XmlData, out var err))
|
|
foreach (var e in err) yield return e;
|
|
}
|
|
}
|
|
|
|
private XDocument GetConfigXmlDoc(FileInfo packageFile)
|
|
{
|
|
var configXmlContent = _packageExtraction.ReadTextFileFromArchive(packageFile, "package.xml", out _);
|
|
|
|
var document = XDocument.Parse(configXmlContent);
|
|
|
|
if (document.Root == null ||
|
|
document.Root.Name.LocalName.Equals("umbPackage") == false)
|
|
throw new FormatException("xml does not have a root node called \"umbPackage\"");
|
|
|
|
return document;
|
|
}
|
|
|
|
private void ValidatePackageFile(FileInfo packageFile, CompiledPackage package)
|
|
{
|
|
if (!(package.Files?.Count > 0)) return;
|
|
|
|
var sourceDestination = _parser.ExtractSourceDestinationFileInformation(package.Files).ToArray();
|
|
|
|
var missingFiles = _packageExtraction.FindMissingFiles(packageFile, sourceDestination.Select(i => i.packageUniqueFile)).ToArray();
|
|
|
|
if (missingFiles.Any())
|
|
{
|
|
throw new Exception("The following file(s) are missing in the package: " +
|
|
string.Join(", ", missingFiles.Select(
|
|
mf =>
|
|
{
|
|
var (packageUniqueFile, appRelativePath) = sourceDestination.Single(fi => fi.packageUniqueFile == mf);
|
|
return $"source: \"{packageUniqueFile}\" destination: \"{appRelativePath}\"";
|
|
})));
|
|
}
|
|
|
|
IEnumerable<string> duplicates = _packageExtraction.FindDuplicateFileNames(packageFile).ToArray();
|
|
|
|
if (duplicates.Any())
|
|
{
|
|
throw new Exception("The following filename(s) are found more than one time in the package, since the filename is used ad primary key, this is not allowed: " +
|
|
string.Join(", ", duplicates));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|