using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Semver; 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 { /// /// 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. /// public class PackagingService : IPackagingService { private readonly IPackageInstallation _packageInstallation; private readonly IAuditService _auditService; private readonly ICreatedPackagesRepository _createdPackages; private readonly IInstalledPackagesRepository _installedPackages; private static HttpClient _httpClient; public PackagingService( IAuditService auditService, ICreatedPackagesRepository createdPackages, IInstalledPackagesRepository installedPackages, IPackageInstallation packageInstallation) { _auditService = auditService; _createdPackages = createdPackages; _installedPackages = installedPackages; _packageInstallation = packageInstallation; } #region Package Files /// public async Task FetchPackageFileAsync(Guid packageId, Version umbracoVersion, int userId) { //includeHidden = true because we don't care if it's hidden we want to get the file regardless var url = $"{Constants.PackageRepository.RestApiBaseUrl}/{packageId}?version={umbracoVersion.ToString(3)}&includeHidden=true&asFile=true"; byte[] bytes; try { if (_httpClient == null) { _httpClient = new HttpClient(); } bytes = await _httpClient.GetByteArrayAsync(url); } catch (HttpRequestException ex) { throw new ConnectionException("An error occurring downloading the package from " + url, ex); } //successful if (bytes.Length > 0) { var packagePath = IOHelper.MapPath(SystemDirectories.Packages); // Check for package directory if (Directory.Exists(packagePath) == false) Directory.CreateDirectory(packagePath); var packageFilePath = Path.Combine(packagePath, packageId + ".umb"); using (var fs1 = new FileStream(packageFilePath, FileMode.Create)) { fs1.Write(bytes, 0, bytes.Length); return new FileInfo(packageFilePath); } } _auditService.Add(AuditType.PackagerInstall, userId, -1, "Package", $"Package {packageId} fetched from {Constants.PackageRepository.DefaultRepositoryId}"); return null; } #endregion #region Installation public CompiledPackage GetCompiledPackageInfo(FileInfo packageFile) => _packageInstallation.ReadPackage(packageFile); public IEnumerable InstallCompiledPackageFiles(PackageDefinition packageDefinition, FileInfo packageFile, int userId = Constants.Security.SuperUserId) { 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"); var compiledPackage = GetCompiledPackageInfo(packageFile); 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}'."); return files; } public InstallationSummary InstallCompiledPackageData(PackageDefinition packageDefinition, FileInfo packageFile, int userId = Constants.Security.SuperUserId) { 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"); var compiledPackage = GetCompiledPackageInfo(packageFile); if (compiledPackage == null) throw new InvalidOperationException("Could not read the package file " + packageFile); if (ImportingPackage.IsRaisedEventCancelled(new ImportPackageEventArgs(packageFile.Name, compiledPackage), this)) return new InstallationSummary { MetaData = compiledPackage }; var summary = _packageInstallation.InstallPackageData(packageDefinition, compiledPackage, userId); SaveInstalledPackage(packageDefinition); _auditService.Add(AuditType.PackagerInstall, userId, -1, "Package", $"Package data installed for package '{compiledPackage.Name}'."); ImportedPackage.RaiseEvent(new ImportPackageEventArgs(summary, compiledPackage, false), this); return summary; } public UninstallationSummary UninstallPackage(string packageName, int userId = Constants.Security.SuperUserId) { //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] }; var allSummaries = new List(); 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(allSummaries, false), this); return summary; } #endregion #region Created/Installed Package Repositories public void DeleteCreatedPackage(int id, int userId = Constants.Security.SuperUserId) { 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); } public IEnumerable GetAllCreatedPackages() => _createdPackages.GetAll(); public PackageDefinition GetCreatedPackageById(int id) => _createdPackages.GetById(id); public bool SaveCreatedPackage(PackageDefinition definition) => _createdPackages.SavePackage(definition); public string ExportCreatedPackage(PackageDefinition definition) => _createdPackages.ExportPackage(definition); public IEnumerable GetAllInstalledPackages() => _installedPackages.GetAll(); public PackageDefinition GetInstalledPackageById(int id) => _installedPackages.GetById(id); public IEnumerable GetInstalledPackageByName(string name) { var found = _installedPackages.GetAll().Where(x => x.Name.InvariantEquals(name)).OrderByDescending(x => SemVersion.Parse(x.Version)); 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)); //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)) 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; } public bool SaveInstalledPackage(PackageDefinition definition) => _installedPackages.SavePackage(definition); public void DeleteInstalledPackage(int packageId, int userId = Constants.Security.SuperUserId) { 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); } #endregion #region Event Handlers /// /// Occurs before Importing umbraco package /// public static event TypedEventHandler> ImportingPackage; /// /// Occurs after a package is imported /// public static event TypedEventHandler> ImportedPackage; /// /// Occurs after a package is uninstalled /// public static event TypedEventHandler UninstalledPackage; #endregion } }