* Gather all notifications in Umbraco.Cms.Core.Notifications * Rename notifications to match convention * Move and rename missed notifications * Move the three remaining public notification into Umbraco.Cms.Core.Notifications
250 lines
12 KiB
C#
250 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Threading.Tasks;
|
|
using Umbraco.Cms.Core.Events;
|
|
using Umbraco.Cms.Core.Hosting;
|
|
using Umbraco.Cms.Core.Models;
|
|
using Umbraco.Cms.Core.Models.Packaging;
|
|
using Umbraco.Cms.Core.Notifications;
|
|
using Umbraco.Cms.Core.Packaging;
|
|
using Umbraco.Cms.Core.Semver;
|
|
using Umbraco.Extensions;
|
|
|
|
namespace Umbraco.Cms.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
|
|
{
|
|
private readonly IPackageInstallation _packageInstallation;
|
|
private readonly IHostingEnvironment _hostingEnvironment;
|
|
private readonly IEventAggregator _eventAggregator;
|
|
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,
|
|
IHostingEnvironment hostingEnvironment,
|
|
IEventAggregator eventAggregator)
|
|
{
|
|
_auditService = auditService;
|
|
_createdPackages = createdPackages;
|
|
_installedPackages = installedPackages;
|
|
_packageInstallation = packageInstallation;
|
|
_hostingEnvironment = hostingEnvironment;
|
|
_eventAggregator = eventAggregator;
|
|
}
|
|
|
|
#region Package Files
|
|
|
|
/// <inheritdoc />
|
|
public async Task<FileInfo> 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 = $"{Cms.Core.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 HttpRequestException("An error occurring downloading the package from " + url, ex);
|
|
}
|
|
|
|
//successful
|
|
if (bytes.Length > 0)
|
|
{
|
|
var packagePath = _hostingEnvironment.MapPathContentRoot(Cms.Core.Constants.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 {Cms.Core.Constants.PackageRepository.DefaultRepositoryId}");
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Installation
|
|
|
|
public CompiledPackage GetCompiledPackageInfo(FileInfo packageFile) => _packageInstallation.ReadPackage(packageFile);
|
|
|
|
public IEnumerable<string> InstallCompiledPackageFiles(PackageDefinition packageDefinition, FileInfo packageFile, int userId = Cms.Core.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 = Cms.Core.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);
|
|
|
|
// Trigger the Importing Package Notification and stop execution if event/user is cancelling it
|
|
var importingPackageNotification = new ImportingPackageNotification(packageFile.Name, compiledPackage);
|
|
if (_eventAggregator.PublishCancelable(importingPackageNotification))
|
|
{
|
|
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}'.");
|
|
|
|
// trigger the ImportedPackage event
|
|
_eventAggregator.Publish(new ImportedPackageNotification(summary, compiledPackage).WithStateFrom(importingPackageNotification));
|
|
|
|
return summary;
|
|
}
|
|
|
|
public UninstallationSummary UninstallPackage(string packageName, int userId = Cms.Core.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<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);
|
|
}
|
|
|
|
// trigger the UninstalledPackage event
|
|
_eventAggregator.Publish(new UninstallPackageNotification(allSummaries));
|
|
|
|
return summary;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Created/Installed Package Repositories
|
|
|
|
public void DeleteCreatedPackage(int id, int userId = Cms.Core.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<PackageDefinition> 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<PackageDefinition> GetAllInstalledPackages() => _installedPackages.GetAll();
|
|
|
|
public PackageDefinition GetInstalledPackageById(int id) => _installedPackages.GetById(id);
|
|
|
|
public IEnumerable<PackageDefinition> 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 = Cms.Core.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
|
|
}
|
|
}
|