2017-12-28 09:18:09 +01:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Net.Http ;
2019-01-11 14:30:04 +11:00
using System.Threading.Tasks ;
2019-01-16 16:27:51 +11:00
using Semver ;
2019-11-08 07:51:14 +01:00
using Umbraco.Core.Composing ;
2017-12-28 09:18:09 +01:00
using Umbraco.Core.Events ;
using Umbraco.Core.Exceptions ;
using Umbraco.Core.IO ;
using Umbraco.Core.Models ;
using Umbraco.Core.Models.Packaging ;
using Umbraco.Core.Packaging ;
namespace Umbraco.Core.Services.Implement
{
/// <summary>
/// Represents the Packaging Service, which provides import/export functionality for the Core models of the API
/// using xml representation. This is primarily used by the Package functionality.
/// </summary>
public class PackagingService : IPackagingService
{
2019-01-10 12:44:57 +11:00
2019-01-14 17:46:12 +11:00
private readonly IPackageInstallation _packageInstallation ;
2019-01-11 14:30:04 +11:00
private readonly IAuditService _auditService ;
2019-01-11 10:35:37 +11:00
private readonly ICreatedPackagesRepository _createdPackages ;
2019-01-11 14:30:04 +11:00
private readonly IInstalledPackagesRepository _installedPackages ;
2018-10-01 14:32:46 +02:00
private static HttpClient _httpClient ;
2017-12-28 09:18:09 +01:00
public PackagingService (
2019-01-11 14:30:04 +11:00
IAuditService auditService ,
ICreatedPackagesRepository createdPackages ,
2019-01-14 17:46:12 +11:00
IInstalledPackagesRepository installedPackages ,
IPackageInstallation packageInstallation )
2019-01-16 17:25:46 +11:00
{
2019-01-11 14:30:04 +11:00
_auditService = auditService ;
2019-01-11 10:35:37 +11:00
_createdPackages = createdPackages ;
2019-01-11 14:30:04 +11:00
_installedPackages = installedPackages ;
2019-01-14 17:46:12 +11:00
_packageInstallation = packageInstallation ;
2017-12-28 09:18:09 +01:00
}
#region Package Files
2019-01-11 14:30:04 +11:00
/// <inheritdoc />
2019-01-15 22:08:08 +11:00
public async Task < FileInfo > FetchPackageFileAsync ( Guid packageId , Version umbracoVersion , int userId )
2017-12-28 09:18:09 +01:00
{
2019-01-11 14:30:04 +11:00
//includeHidden = true because we don't care if it's hidden we want to get the file regardless
2019-11-05 13:45:42 +01:00
var url = $"{Constants.PackageRepository.RestApiBaseUrl}/{packageId}?version={umbracoVersion.ToString(3)}&includeHidden=true&asFile=true" ;
2019-01-11 14:30:04 +11:00
byte [ ] bytes ;
try
2017-12-28 09:18:09 +01:00
{
2019-01-11 14:30:04 +11:00
if ( _httpClient = = null )
2017-12-28 09:18:09 +01:00
{
2019-01-11 14:30:04 +11:00
_httpClient = new HttpClient ( ) ;
2017-12-28 09:18:09 +01:00
}
2019-01-11 14:30:04 +11:00
bytes = await _httpClient . GetByteArrayAsync ( url ) ;
}
catch ( HttpRequestException ex )
{
2019-01-22 18:03:39 -05:00
throw new ConnectionException ( "An error occurring downloading the package from " + url , ex ) ;
2019-01-11 14:30:04 +11:00
}
2017-12-28 09:18:09 +01:00
2019-01-22 18:03:39 -05:00
//successful
2019-01-11 14:30:04 +11:00
if ( bytes . Length > 0 )
{
2019-11-08 07:51:14 +01:00
var packagePath = Current . IOHelper . MapPath ( SystemDirectories . Packages ) ;
2017-12-28 09:18:09 +01:00
2019-01-11 14:30:04 +11:00
// Check for package directory
if ( Directory . Exists ( packagePath ) = = false )
Directory . CreateDirectory ( packagePath ) ;
2017-12-28 09:18:09 +01:00
2019-01-11 14:30:04 +11:00
var packageFilePath = Path . Combine ( packagePath , packageId + ".umb" ) ;
2017-12-28 09:18:09 +01:00
2019-01-11 14:30:04 +11:00
using ( var fs1 = new FileStream ( packageFilePath , FileMode . Create ) )
{
fs1 . Write ( bytes , 0 , bytes . Length ) ;
2019-01-15 22:08:08 +11:00
return new FileInfo ( packageFilePath ) ;
2017-12-28 09:18:09 +01:00
}
}
2019-11-05 13:45:42 +01:00
_auditService . Add ( AuditType . PackagerInstall , userId , - 1 , "Package" , $"Package {packageId} fetched from {Constants.PackageRepository.DefaultRepositoryId}" ) ;
2019-01-11 14:30:04 +11:00
return null ;
2017-12-28 09:18:09 +01:00
}
#endregion
#region Installation
2019-01-15 22:08:08 +11:00
public CompiledPackage GetCompiledPackageInfo ( FileInfo packageFile ) = > _packageInstallation . ReadPackage ( packageFile ) ;
2019-01-10 12:44:57 +11:00
2019-11-05 13:45:42 +01:00
public IEnumerable < string > InstallCompiledPackageFiles ( PackageDefinition packageDefinition , FileInfo packageFile , int userId = Constants . Security . SuperUserId )
2017-12-28 09:18:09 +01:00
{
2019-01-14 14:28:00 +11:00
if ( packageDefinition = = null ) throw new ArgumentNullException ( nameof ( packageDefinition ) ) ;
if ( packageDefinition . Id = = default ) throw new ArgumentException ( "The package definition has not been persisted" ) ;
if ( packageDefinition . Name = = default ) throw new ArgumentException ( "The package definition has incomplete information" ) ;
2017-12-28 09:18:09 +01:00
2019-01-15 22:08:08 +11:00
var compiledPackage = GetCompiledPackageInfo ( packageFile ) ;
if ( compiledPackage = = null ) throw new InvalidOperationException ( "Could not read the package file " + packageFile ) ;
2018-03-22 17:41:13 +01:00
2019-01-15 22:08:08 +11:00
var files = _packageInstallation . InstallPackageFiles ( packageDefinition , compiledPackage , userId ) . ToList ( ) ;
2019-01-16 17:25:46 +11:00
2019-01-14 14:28:00 +11:00
SaveInstalledPackage ( packageDefinition ) ;
2017-12-28 09:18:09 +01:00
2019-01-14 17:46:12 +11:00
_auditService . Add ( AuditType . PackagerInstall , userId , - 1 , "Package" , $"Package files installed for package '{compiledPackage.Name}'." ) ;
2017-12-28 09:18:09 +01:00
2019-01-14 14:28:00 +11:00
return files ;
2017-12-28 09:18:09 +01:00
}
2019-11-05 13:45:42 +01:00
public InstallationSummary InstallCompiledPackageData ( PackageDefinition packageDefinition , FileInfo packageFile , int userId = Constants . Security . SuperUserId )
2017-12-28 09:18:09 +01:00
{
2019-01-14 14:28:00 +11:00
if ( packageDefinition = = null ) throw new ArgumentNullException ( nameof ( packageDefinition ) ) ;
if ( packageDefinition . Id = = default ) throw new ArgumentException ( "The package definition has not been persisted" ) ;
if ( packageDefinition . Name = = default ) throw new ArgumentException ( "The package definition has incomplete information" ) ;
2017-12-28 09:18:09 +01:00
2019-01-15 22:08:08 +11:00
var compiledPackage = GetCompiledPackageInfo ( packageFile ) ;
if ( compiledPackage = = null ) throw new InvalidOperationException ( "Could not read the package file " + packageFile ) ;
2019-01-14 14:28:00 +11:00
2019-01-15 22:08:08 +11:00
if ( ImportingPackage . IsRaisedEventCancelled ( new ImportPackageEventArgs < string > ( packageFile . Name , compiledPackage ) , this ) )
2019-01-14 14:28:00 +11:00
return new InstallationSummary { MetaData = compiledPackage } ;
var summary = _packageInstallation . InstallPackageData ( packageDefinition , compiledPackage , userId ) ;
2019-01-14 17:46:12 +11:00
SaveInstalledPackage ( packageDefinition ) ;
2019-01-14 14:28:00 +11:00
_auditService . Add ( AuditType . PackagerInstall , userId , - 1 , "Package" , $"Package data installed for package '{compiledPackage.Name}'." ) ;
ImportedPackage . RaiseEvent ( new ImportPackageEventArgs < InstallationSummary > ( summary , compiledPackage , false ) , this ) ;
return summary ;
2017-12-28 09:18:09 +01:00
}
2019-11-05 13:45:42 +01:00
public UninstallationSummary UninstallPackage ( string packageName , int userId = Constants . Security . SuperUserId )
2019-01-15 22:08:08 +11:00
{
2019-01-16 17:25:46 +11:00
//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 ) ;
var summary = new UninstallationSummary
{
MetaData = allPackageVersions [ 0 ]
} ;
2019-01-15 22:08:08 +11:00
2019-01-16 17:25:46 +11:00
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 ) ;
}
2019-01-15 22:08:08 +11:00
// trigger the UninstalledPackage event
2019-01-16 17:25:46 +11:00
UninstalledPackage . RaiseEvent ( new UninstallPackageEventArgs ( allSummaries , false ) , this ) ;
2019-01-15 22:08:08 +11:00
return summary ;
}
2017-12-28 09:18:09 +01:00
#endregion
2019-01-11 14:30:04 +11:00
#region Created / Installed Package Repositories
2019-01-10 12:44:57 +11:00
2019-11-05 13:45:42 +01:00
public void DeleteCreatedPackage ( int id , int userId = Constants . Security . SuperUserId )
2019-01-11 14:30:04 +11:00
{
var package = GetCreatedPackageById ( id ) ;
if ( package = = null ) return ;
_auditService . Add ( AuditType . PackagerUninstall , userId , - 1 , "Package" , $"Created package '{package.Name}' deleted. Package id: {package.Id}" ) ;
_createdPackages . Delete ( id ) ;
}
2019-01-10 17:18:47 +11:00
2019-01-11 10:35:37 +11:00
public IEnumerable < PackageDefinition > GetAllCreatedPackages ( ) = > _createdPackages . GetAll ( ) ;
2019-01-10 17:18:47 +11:00
2019-01-11 10:35:37 +11:00
public PackageDefinition GetCreatedPackageById ( int id ) = > _createdPackages . GetById ( id ) ;
2019-01-10 17:18:47 +11:00
2019-01-11 10:35:37 +11:00
public bool SaveCreatedPackage ( PackageDefinition definition ) = > _createdPackages . SavePackage ( definition ) ;
2019-01-10 17:18:47 +11:00
2019-01-11 10:35:37 +11:00
public string ExportCreatedPackage ( PackageDefinition definition ) = > _createdPackages . ExportPackage ( definition ) ;
2019-01-10 12:44:57 +11:00
2019-01-11 14:30:04 +11:00
public IEnumerable < PackageDefinition > GetAllInstalledPackages ( ) = > _installedPackages . GetAll ( ) ;
public PackageDefinition GetInstalledPackageById ( int id ) = > _installedPackages . GetById ( id ) ;
2019-01-16 17:25:46 +11:00
public IEnumerable < PackageDefinition > GetInstalledPackageByName ( string name )
2019-01-16 16:27:51 +11:00
{
2019-01-16 17:25:46 +11:00
var found = _installedPackages . GetAll ( ) . Where ( x = > x . Name . InvariantEquals ( name ) ) . OrderByDescending ( x = > SemVersion . Parse ( x . Version ) ) ;
2019-01-16 16:27:51 +11:00
return found ;
}
public PackageInstallType GetPackageInstallType ( string packageName , SemVersion packageVersion , out PackageDefinition alreadyInstalled )
{
if ( packageName = = null ) throw new ArgumentNullException ( nameof ( packageName ) ) ;
if ( packageVersion = = null ) throw new ArgumentNullException ( nameof ( packageVersion ) ) ;
2019-11-05 12:54:22 +01:00
//get the latest version installed
2019-01-16 17:25:46 +11:00
alreadyInstalled = GetInstalledPackageByName ( packageName ) ? . OrderByDescending ( x = > SemVersion . Parse ( x . Version ) ) . FirstOrDefault ( ) ;
2019-01-16 16:27:51 +11:00
if ( alreadyInstalled = = null ) return PackageInstallType . NewInstall ;
if ( ! SemVersion . TryParse ( alreadyInstalled . Version , out var installedVersion ) )
throw new InvalidOperationException ( "Could not parse the currently installed package version " + alreadyInstalled . Version ) ;
//compare versions
if ( installedVersion > = packageVersion ) return PackageInstallType . AlreadyInstalled ;
//it's an upgrade
return PackageInstallType . Upgrade ;
}
2019-01-11 14:30:04 +11:00
public bool SaveInstalledPackage ( PackageDefinition definition ) = > _installedPackages . SavePackage ( definition ) ;
2019-11-05 13:45:42 +01:00
public void DeleteInstalledPackage ( int packageId , int userId = Constants . Security . SuperUserId )
2019-01-11 14:30:04 +11:00
{
var package = GetInstalledPackageById ( packageId ) ;
if ( package = = null ) return ;
_auditService . Add ( AuditType . PackagerUninstall , userId , - 1 , "Package" , $"Installed package '{package.Name}' deleted. Package id: {package.Id}" ) ;
_installedPackages . Delete ( packageId ) ;
}
2017-12-28 09:18:09 +01:00
#endregion
#region Event Handlers
/// <summary>
/// Occurs before Importing umbraco package
/// </summary>
2019-01-15 22:08:08 +11:00
public static event TypedEventHandler < IPackagingService , ImportPackageEventArgs < string > > ImportingPackage ;
2017-12-28 09:18:09 +01:00
/// <summary>
/// Occurs after a package is imported
/// </summary>
public static event TypedEventHandler < IPackagingService , ImportPackageEventArgs < InstallationSummary > > ImportedPackage ;
/// <summary>
/// Occurs after a package is uninstalled
/// </summary>
2019-01-16 17:25:46 +11:00
public static event TypedEventHandler < IPackagingService , UninstallPackageEventArgs > UninstalledPackage ;
2017-12-28 09:18:09 +01:00
#endregion
2019-01-10 12:44:57 +11:00
2019-01-16 17:25:46 +11:00
2017-12-28 09:18:09 +01:00
}
}