2018-06-29 19:52:40 +02:00
using System ;
using System.Collections.Generic ;
2019-01-14 17:46:12 +11:00
using System.Globalization ;
2018-06-29 19:52:40 +02:00
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
{
2019-01-14 14:28:00 +11:00
private readonly PackageExtraction _packageExtraction ;
2019-01-14 17:46:12 +11:00
private readonly PackageDataInstallation _packageDataInstallation ;
2019-01-14 14:28:00 +11:00
private readonly PackageFileInstallation _packageFileInstallation ;
private readonly CompiledPackageXmlParser _parser ;
2019-01-14 17:46:12 +11:00
private readonly IPackageActionRunner _packageActionRunner ;
2019-01-14 14:28:00 +11:00
private readonly DirectoryInfo _applicationRootFolder ;
2018-06-29 19:52:40 +02:00
2019-01-14 14:28:00 +11:00
/// <summary>
/// Constructor
/// </summary>
2019-01-14 17:46:12 +11:00
/// <param name="packageDataInstallation"></param>
2019-01-14 14:28:00 +11:00
/// <param name="packageFileInstallation"></param>
/// <param name="parser"></param>
2019-01-14 17:46:12 +11:00
/// <param name="packageActionRunner"></param>
2019-01-14 14:28:00 +11:00
/// <param name="applicationRootFolder">
/// The root folder of the application
/// </param>
2019-01-14 17:46:12 +11:00
public PackageInstallation ( PackageDataInstallation packageDataInstallation , PackageFileInstallation packageFileInstallation , CompiledPackageXmlParser parser , IPackageActionRunner packageActionRunner ,
2019-01-17 10:56:24 +11:00
DirectoryInfo applicationRootFolder )
2019-01-14 14:28:00 +11:00
{
_packageExtraction = new PackageExtraction ( ) ;
2019-01-14 17:46:12 +11:00
_packageFileInstallation = packageFileInstallation ? ? throw new ArgumentNullException ( nameof ( packageFileInstallation ) ) ;
_packageDataInstallation = packageDataInstallation ? ? throw new ArgumentNullException ( nameof ( packageDataInstallation ) ) ;
2019-01-15 22:08:08 +11:00
_parser = parser ? ? throw new ArgumentNullException ( nameof ( parser ) ) ;
_packageActionRunner = packageActionRunner ? ? throw new ArgumentNullException ( nameof ( packageActionRunner ) ) ;
_applicationRootFolder = applicationRootFolder ? ? throw new ArgumentNullException ( nameof ( applicationRootFolder ) ) ;
2019-01-14 14:28:00 +11:00
}
2019-01-15 22:08:08 +11:00
public CompiledPackage ReadPackage ( FileInfo packageFile )
2019-01-14 14:28:00 +11:00
{
2019-01-15 22:08:08 +11:00
if ( packageFile = = null ) throw new ArgumentNullException ( nameof ( packageFile ) ) ;
var doc = GetConfigXmlDoc ( packageFile ) ;
2019-01-14 14:28:00 +11:00
2019-01-15 22:08:08 +11:00
var compiledPackage = _parser . ToCompiledPackage ( doc , packageFile , _applicationRootFolder . FullName ) ;
2019-01-14 14:28:00 +11:00
2019-01-15 22:08:08 +11:00
ValidatePackageFile ( packageFile , compiledPackage ) ;
2019-01-14 14:28:00 +11:00
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" ) ;
2019-01-15 22:08:08 +11:00
var packageZipFile = compiledPackage . PackageFile ;
2019-01-14 14:28:00 +11:00
2019-01-17 10:56:24 +11:00
var files = _packageFileInstallation . InstallFiles ( compiledPackage , packageZipFile , _applicationRootFolder . FullName ) . ToList ( ) ;
2019-01-15 23:10:08 +11:00
packageDefinition . Files = files ;
return files ;
2019-01-14 14:28:00 +11:00
}
2019-01-16 17:25:46 +11:00
/// <inheritdoc />
2019-01-15 22:08:08 +11:00
public UninstallationSummary UninstallPackage ( PackageDefinition package , int userId )
2019-01-14 14:28:00 +11:00
{
2019-01-15 22:08:08 +11:00
//running this will update the PackageDefinition with the items being removed
var summary = _packageDataInstallation . UninstallPackageData ( package , userId ) ;
2019-01-15 22:59:13 +11:00
2019-01-16 17:25:46 +11:00
summary . Actions = CompiledPackageXmlParser . GetPackageActions ( XElement . Parse ( package . Actions ) , package . Name ) ;
2019-01-15 22:08:08 +11:00
//run actions before files are removed
summary . ActionErrors = UndoPackageActions ( package , summary . Actions ) . ToList ( ) ;
2019-01-14 14:28:00 +11:00
2019-01-15 22:08:08 +11:00
var filesRemoved = _packageFileInstallation . UninstallFiles ( package ) ;
summary . FilesUninstalled = filesRemoved ;
return summary ;
}
public InstallationSummary InstallPackageData ( PackageDefinition packageDefinition , CompiledPackage compiledPackage , int userId )
{
2019-01-14 17:46:12 +11:00
var installationSummary = new InstallationSummary
{
DataTypesInstalled = _packageDataInstallation . ImportDataTypes ( compiledPackage . DataTypes . ToList ( ) , userId ) ,
LanguagesInstalled = _packageDataInstallation . ImportLanguages ( compiledPackage . Languages , userId ) ,
DictionaryItemsInstalled = _packageDataInstallation . ImportDictionaryItems ( compiledPackage . DictionaryItems , userId ) ,
MacrosInstalled = _packageDataInstallation . ImportMacros ( compiledPackage . Macros , userId ) ,
TemplatesInstalled = _packageDataInstallation . ImportTemplates ( compiledPackage . Templates . ToList ( ) , userId ) ,
DocumentTypesInstalled = _packageDataInstallation . ImportDocumentTypes ( compiledPackage . DocumentTypes , userId )
} ;
2019-01-14 14:28:00 +11:00
2019-01-14 17:46:12 +11:00
//we need a reference to the imported doc types to continue
var importedDocTypes = installationSummary . DocumentTypesInstalled . ToDictionary ( x = > x . Alias , x = > x ) ;
installationSummary . StylesheetsInstalled = _packageDataInstallation . ImportStylesheets ( compiledPackage . Stylesheets , userId ) ;
installationSummary . ContentInstalled = _packageDataInstallation . ImportContent ( compiledPackage . Documents , importedDocTypes , userId ) ;
2019-01-16 17:25:46 +11:00
installationSummary . Actions = CompiledPackageXmlParser . GetPackageActions ( XElement . Parse ( compiledPackage . Actions ) , compiledPackage . Name ) ;
2019-01-14 17:46:12 +11:00
installationSummary . MetaData = compiledPackage ;
installationSummary . FilesInstalled = packageDefinition . Files ;
2019-01-15 22:08:08 +11:00
2019-01-14 17:46:12 +11:00
//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 ;
2019-01-15 22:08:08 +11:00
//run package actions
installationSummary . ActionErrors = RunPackageActions ( packageDefinition , installationSummary . Actions ) . ToList ( ) ;
2019-01-14 17:46:12 +11:00
return installationSummary ;
}
2018-06-29 19:52:40 +02:00
2019-01-15 22:08:08 +11:00
private IEnumerable < string > RunPackageActions ( PackageDefinition packageDefinition , IEnumerable < PackageAction > actions )
2018-06-29 19:52:40 +02:00
{
2019-01-14 17:46:12 +11:00
foreach ( var n in actions )
{
2019-01-15 22:08:08 +11:00
//if there is an undo section then save it to the definition so we can run it at uninstallation
2019-01-14 17:46:12 +11:00
var undo = n . Undo ;
if ( undo )
packageDefinition . Actions + = n . XmlData . ToString ( ) ;
//Run the actions tagged only for 'install'
if ( n . RunAt ! = ActionRunAt . Install ) continue ;
2019-01-15 22:08:08 +11:00
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 ;
2019-01-14 17:46:12 +11:00
}
2018-06-29 19:52:40 +02:00
}
2019-01-15 22:08:08 +11:00
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 ;
}
}
2019-01-14 17:46:12 +11:00
2019-01-14 14:28:00 +11:00
private XDocument GetConfigXmlDoc ( FileInfo packageFile )
2018-06-29 19:52:40 +02:00
{
2019-01-14 17:46:12 +11:00
var configXmlContent = _packageExtraction . ReadTextFileFromArchive ( packageFile , "package.xml" , out _ ) ;
2018-06-29 19:52:40 +02:00
2019-01-14 14:28:00 +11:00
var document = XDocument . Parse ( configXmlContent ) ;
2018-06-29 19:52:40 +02:00
2019-01-14 14:28:00 +11:00
if ( document . Root = = null | |
2019-01-14 17:46:12 +11:00
document . Root . Name . LocalName . Equals ( "umbPackage" ) = = false )
2019-01-14 14:28:00 +11:00
throw new FormatException ( "xml does not have a root node called \"umbPackage\"" ) ;
2018-06-29 19:52:40 +02:00
2019-01-14 14:28:00 +11:00
return document ;
2018-06-29 19:52:40 +02:00
}
2019-01-14 14:28:00 +11:00
private void ValidatePackageFile ( FileInfo packageFile , CompiledPackage package )
2018-06-29 19:52:40 +02:00
{
2019-01-14 14:28:00 +11:00
if ( ! ( package . Files ? . Count > 0 ) ) return ;
2018-06-29 19:52:40 +02:00
2019-01-14 14:28:00 +11:00
var sourceDestination = _parser . ExtractSourceDestinationFileInformation ( package . Files ) . ToArray ( ) ;
2018-06-29 19:52:40 +02:00
2019-01-14 14:28:00 +11:00
var missingFiles = _packageExtraction . FindMissingFiles ( packageFile , sourceDestination . Select ( i = > i . packageUniqueFile ) ) . ToArray ( ) ;
2018-06-29 19:52:40 +02:00
2019-01-14 14:28:00 +11:00
if ( missingFiles . Any ( ) )
2018-06-29 19:52:40 +02:00
{
2019-01-14 14:28:00 +11:00
throw new Exception ( "The following file(s) are missing in the package: " +
string . Join ( ", " , missingFiles . Select (
mf = >
{
2019-01-14 17:46:12 +11:00
var ( packageUniqueFile , appRelativePath ) = sourceDestination . Single ( fi = > fi . packageUniqueFile = = mf ) ;
return $"source: \" { packageUniqueFile } \ " destination: \"{appRelativePath}\"" ;
2019-01-14 14:28:00 +11:00
} ) ) ) ;
2018-06-29 19:52:40 +02:00
}
2019-01-14 14:28:00 +11:00
IEnumerable < string > duplicates = _packageExtraction . FindDuplicateFileNames ( packageFile ) . ToArray ( ) ;
2018-06-29 19:52:40 +02:00
2019-01-14 14:28:00 +11:00
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 ) ) ;
2018-06-29 19:52:40 +02:00
}
}
2019-01-14 17:46:12 +11:00
2019-01-14 14:28:00 +11:00
2018-06-29 19:52:40 +02:00
}
}