#17273: Making the default install action hook up into the upgrade code if the package is already installed. Merging the code paths into the packageinstaller. Adding some UT coverage to the package installer experience.

--HG--
branch : 1.x
This commit is contained in:
Andre Rodrigues
2011-04-03 16:12:13 -07:00
parent fd3926c650
commit 012874826b
14 changed files with 440 additions and 327 deletions

View File

@@ -54,6 +54,7 @@ namespace Orchard.Tests.Modules.Indexing {
if (Directory.Exists(_basePath)) {
Directory.Delete(_basePath, true);
}
Directory.CreateDirectory(_basePath);
_contentDefinitionManager = new Mock<IContentDefinitionManager>();
_appDataFolder = AppDataFolderTests.CreateAppDataFolder(_basePath);
@@ -227,6 +228,7 @@ namespace Orchard.Tests.Modules.Indexing {
}
#region Stubs
public class ThingHandler : ContentHandler {
public ThingHandler() {
Filters.Add(new ActivatingFilter<Thing>(ThingDriver.ContentTypeName));
@@ -278,6 +280,7 @@ namespace Orchard.Tests.Modules.Indexing {
});
}
}
#endregion
}
}

View File

@@ -149,6 +149,8 @@
<Compile Include="Indexing\LuceneIndexProviderTests.cs" />
<Compile Include="Indexing\LuceneSearchBuilderTests.cs" />
<Compile Include="Media\Services\MediaServiceTests.cs" />
<Compile Include="Packaging\Services\FolderUpdaterTests.cs" />
<Compile Include="Packaging\Services\PackageInstallerTests.cs" />
<Compile Include="Recipes\RecipeHandlers\ModuleRecipeHandlerTest.cs" />
<Compile Include="Recipes\RecipeHandlers\ThemeRecipeHandlerTest.cs" />
<Compile Include="Recipes\Services\RecipeManagerTests.cs" />

View File

@@ -0,0 +1,53 @@
using System.IO;
using Autofac;
using Moq;
using NUnit.Framework;
using Orchard.Packaging.Services;
using Orchard.UI.Notify;
namespace Orchard.Tests.Modules.Packaging.Services {
[TestFixture]
public class FolderUpdaterTests {
protected IContainer _container;
private readonly string _basePath = Path.Combine(Path.GetTempPath(), "FolderUpdaterTests");
[SetUp]
public virtual void Init() {
if (Directory.Exists(_basePath)) {
Directory.Delete(_basePath, true);
}
Directory.CreateDirectory(_basePath);
var builder = new ContainerBuilder();
builder.RegisterType<FolderUpdater>().As<IFolderUpdater>();
builder.RegisterInstance(new Mock<INotifier>().Object);
_container = builder.Build();
}
[TestFixtureTearDown]
public void Clean() {
if (Directory.Exists(_basePath)) {
Directory.Delete(_basePath, true);
}
}
[Test]
public void BackupTest() {
DirectoryInfo sourceDirectoryInfo = Directory.CreateDirectory(Path.Combine(_basePath, "Source"));
File.CreateText(Path.Combine(sourceDirectoryInfo.FullName, "file1.txt")).Close();
File.CreateText(Path.Combine(sourceDirectoryInfo.FullName, "file2.txt")).Close();
IFolderUpdater folderUpdater = _container.Resolve<IFolderUpdater>();
DirectoryInfo targetDirectoryInfo = new DirectoryInfo(Path.Combine(_basePath, "Target"));
folderUpdater.Backup(sourceDirectoryInfo, targetDirectoryInfo);
Assert.That(File.Exists(Path.Combine(targetDirectoryInfo.FullName, "file1.txt")), Is.True);
Assert.That(File.Exists(Path.Combine(targetDirectoryInfo.FullName, "file2.txt")), Is.True);
}
}
}

View File

@@ -0,0 +1,173 @@
using System.IO;
using Autofac;
using Moq;
using NuGet;
using NUnit.Framework;
using Orchard.Caching;
using Orchard.Environment;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.VirtualPath;
using Orchard.FileSystems.WebSite;
using Orchard.Packaging.Services;
using Orchard.Tests.Stubs;
using Orchard.UI.Notify;
using IPackageBuilder = Orchard.Packaging.Services.IPackageBuilder;
using PackageBuilder = Orchard.Packaging.Services.PackageBuilder;
namespace Orchard.Tests.Modules.Packaging.Services {
[TestFixture]
public class PackageInstallerTests : ContainerTestBase {
private const string PackageIdentifier = "Hello.World";
private readonly string _basePath = Path.Combine(Path.GetTempPath(), "PackageInstallerTests");
private Mock<IVirtualPathProvider> _mockedVirtualPathProvider;
protected override void Register(ContainerBuilder builder) {
builder.RegisterType<PackageBuilder>().As<IPackageBuilder>();
builder.RegisterType<PackageInstaller>().As<IPackageInstaller>();
builder.RegisterType<ExtensionManager>().As<IExtensionManager>();
builder.RegisterType<FolderUpdater>().As<IFolderUpdater>();
builder.RegisterInstance(new Mock<INotifier>().Object);
builder.RegisterType<StubCacheManager>().As<ICacheManager>();
_mockedVirtualPathProvider = new Mock<IVirtualPathProvider>();
builder.RegisterInstance(_mockedVirtualPathProvider.Object).As<IVirtualPathProvider>();
builder.RegisterType<DefaultOrchardFrameworkAssemblies>().As<IOrchardFrameworkAssemblies>();
builder.RegisterType<InMemoryWebSiteFolder>().As<IWebSiteFolder>()
.As<InMemoryWebSiteFolder>().InstancePerLifetimeScope();
}
[SetUp]
public override void Init() {
base.Init();
if (Directory.Exists(_basePath)) {
Directory.Delete(_basePath, true);
}
Directory.CreateDirectory(_basePath);
}
[TestFixtureTearDown]
public void Clean() {
if (Directory.Exists(_basePath)) {
Directory.Delete(_basePath, true);
}
}
private Stream BuildHelloWorld(IPackageBuilder packageBuilder) {
// add some content because NuGet requires it
var folder = _container.Resolve<InMemoryWebSiteFolder>();
using (var sourceStream = GetType().Assembly.GetManifestResourceStream(GetType(), "Hello.World.csproj.txt")) {
folder.AddFile("~/Modules/Hello.World/Hello.World.csproj", new StreamReader(sourceStream).ReadToEnd());
}
return packageBuilder.BuildPackage(new ExtensionDescriptor {
ExtensionType = DefaultExtensionTypes.Module,
Id = PackageIdentifier,
Version = "1.0",
Description = "a",
Author = "b"
});
}
[Test]
public void InstallTest() {
IPackageBuilder packageBuilder = _container.Resolve<IPackageBuilder>();
Stream stream = BuildHelloWorld(packageBuilder);
string filename = Path.Combine(_basePath, "package.nupkg");
using (var fileStream = File.Create(filename)) {
stream.CopyTo(fileStream);
}
ZipPackage zipPackage = new ZipPackage(filename);
IPackageInstaller packageInstaller = _container.Resolve<IPackageInstaller>();
_mockedVirtualPathProvider.Setup(v => v.MapPath(It.IsAny<string>()))
.Returns<string>(path => Path.Combine(_basePath, path.Replace("~\\", "")));
_mockedVirtualPathProvider.Setup(v => v.Combine(It.IsAny<string[]>()))
.Returns<string[]>(Path.Combine);
PackageInfo packageInfo = packageInstaller.Install(zipPackage, _basePath, _basePath);
Assert.That(packageInfo, Is.Not.Null);
Assert.That(Directory.Exists(Path.Combine(_basePath, "Modules/Hello.World")));
Assert.That(File.Exists(Path.Combine(_basePath, "Modules/Hello.World/Hello.World.csproj")));
}
[Test]
public void InstallUpgradeTest() {
IPackageBuilder packageBuilder = _container.Resolve<IPackageBuilder>();
Stream stream = BuildHelloWorld(packageBuilder);
string filename = Path.Combine(_basePath, "package.nupkg");
using (var fileStream = File.Create(filename)) {
stream.CopyTo(fileStream);
}
ZipPackage zipPackage = new ZipPackage(filename);
IPackageInstaller packageInstaller = _container.Resolve<IPackageInstaller>();
_mockedVirtualPathProvider.Setup(v => v.MapPath(It.IsAny<string>()))
.Returns<string>(path => Path.Combine(_basePath, path.Replace("~\\", "").Replace("~/", "")));
_mockedVirtualPathProvider.Setup(v => v.Combine(It.IsAny<string[]>()))
.Returns<string[]>(Path.Combine);
PackageInfo packageInfo = packageInstaller.Install(zipPackage, _basePath, _basePath);
Assert.That(packageInfo, Is.Not.Null);
Assert.That(Directory.Exists(Path.Combine(_basePath, "Modules/Hello.World")));
Assert.That(File.Exists(Path.Combine(_basePath, "Modules/Hello.World/Hello.World.csproj")));
// Modify one of the files and install again and check that backup worked and file content is updated
string[] lines = File.ReadAllLines(Path.Combine(_basePath, "Modules/Hello.World/Hello.World.csproj"));
string originalLine = lines[lines.Length - 1];
lines[lines.Length - 1] = "modified";
File.WriteAllLines(Path.Combine(_basePath, "Modules/Hello.World/Hello.World.csproj"), lines);
packageInfo = packageInstaller.Install(zipPackage, _basePath, _basePath);
Assert.That(packageInfo, Is.Not.Null);
Assert.That(Directory.Exists(Path.Combine(_basePath, "Modules/Hello.World")));
Assert.That(File.Exists(Path.Combine(_basePath, "Modules/Hello.World/Hello.World.csproj")));
lines = File.ReadAllLines(Path.Combine(_basePath, "Modules/Hello.World/Hello.World.csproj"));
Assert.That(lines[lines.Length - 1], Is.EqualTo(originalLine));
Assert.That(Directory.Exists(Path.Combine(_basePath, "Modules/_Backup/Hello.World")));
Assert.That(File.Exists(Path.Combine(_basePath, "Modules/_Backup/Hello.World/Hello.World.csproj")));
lines = File.ReadAllLines(Path.Combine(_basePath, "Modules/_Backup/Hello.World/Hello.World.csproj"));
Assert.That(lines[lines.Length - 1], Is.EqualTo("modified"));
}
[Test]
public void UninstallTest() {
IPackageBuilder packageBuilder = _container.Resolve<IPackageBuilder>();
Stream stream = BuildHelloWorld(packageBuilder);
string filename = Path.Combine(_basePath, "package.nupkg");
using (var fileStream = File.Create(filename)) {
stream.CopyTo(fileStream);
}
ZipPackage zipPackage = new ZipPackage(filename);
IPackageInstaller packageInstaller = _container.Resolve<IPackageInstaller>();
_mockedVirtualPathProvider.Setup(v => v.MapPath(It.IsAny<string>()))
.Returns<string>(path => Path.Combine(_basePath, path.Replace("~\\", "").Replace("~/", "")));
_mockedVirtualPathProvider.Setup(v => v.Combine(It.IsAny<string[]>()))
.Returns<string[]>(Path.Combine);
PackageInfo packageInfo = packageInstaller.Install(zipPackage, _basePath, _basePath);
Assert.That(packageInfo, Is.Not.Null);
Assert.That(Directory.Exists(Path.Combine(_basePath, "Modules/Hello.World")));
Assert.That(File.Exists(Path.Combine(_basePath, "Modules/Hello.World/Hello.World.csproj")));
// Uninstall and check that the installation will be gone
packageInstaller.Uninstall(zipPackage.Id, _basePath);
Assert.That(Directory.Exists(Path.Combine(_basePath, "Modules/Hello.World")), Is.False);
}
}
}

View File

@@ -118,7 +118,7 @@ Features:
return Enumerable.Empty<PackagingSource>();
}
public void AddSource(string feedTitle, string feedUrl) {
public int AddSource(string feedTitle, string feedUrl) {
throw new NotImplementedException();
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Orchard.DisplayManagement;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.Localization;
@@ -22,14 +23,14 @@ namespace Orchard.Packaging.Controllers {
[OrchardFeature("Gallery.Updates")]
[Themed, Admin]
public class GalleryUpdatesController : Controller {
private readonly ShellSettings _shellSettings;
private readonly IPackagingSourceManager _packagingSourceManager;
private readonly INotifier _notifier;
private readonly IPackageUpdateService _packageUpdateService;
private readonly IBackgroundPackageUpdateStatus _backgroundPackageUpdateStatus;
private readonly IReportsCoordinator _reportsCoordinator;
private readonly IReportsManager _reportsManager;
public GalleryUpdatesController(IOrchardServices services,
public GalleryUpdatesController(
ShellSettings shellSettings,
IOrchardServices services,
IPackagingSourceManager packagingSourceManager,
INotifier notifier,
IPackageUpdateService packageUpdateService,
@@ -38,12 +39,11 @@ namespace Orchard.Packaging.Controllers {
IReportsManager reportsManager,
IShapeFactory shapeFactory) {
_shellSettings = shellSettings;
_packagingSourceManager = packagingSourceManager;
_notifier = notifier;
_packageUpdateService = packageUpdateService;
_backgroundPackageUpdateStatus = backgroundPackageUpdateStatus;
_reportsCoordinator = reportsCoordinator;
_reportsManager = reportsManager;
Services = services;
Shape = shapeFactory;
@@ -65,14 +65,11 @@ namespace Orchard.Packaging.Controllers {
}
private ActionResult PackageUpdate(string view, string extensionType, int? reportId, PagerParameters pagerParameters) {
if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to install packages")))
if (_shellSettings.Name != ShellSettings.DefaultName || !Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to add sources")))
return new HttpUnauthorizedResult();
Pager pager = new Pager(Services.WorkContext.CurrentSite, pagerParameters);
if (reportId != null)
CreateNotificationsFromReport(reportId.Value);
if (!_packagingSourceManager.GetSources().Any()) {
Services.Notifier.Error(T("No Gallery feed configured"));
return View(view, new PackagingListViewModel { Entries = new List<UpdatePackageEntry>() });
@@ -105,92 +102,5 @@ namespace Orchard.Packaging.Controllers {
Pager = Shape.Pager(pager).TotalItemCount(totalItemCount)
});
}
public ActionResult Install(string packageId, string version, int sourceId, string returnUrl) {
if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to install packages")))
return new HttpUnauthorizedResult();
_backgroundPackageUpdateStatus.Value =
_backgroundPackageUpdateStatus.Value ??
_packageUpdateService.GetPackagesStatus(_packagingSourceManager.GetSources());
var entry = _backgroundPackageUpdateStatus.Value
.Entries
.SelectMany(e => e.PackageVersions)
.Where(e => e.PackageId == packageId && e.Version == version && e.Source.Id == sourceId)
.FirstOrDefault();
if (entry == null) {
return HttpNotFound();
}
try {
_packageUpdateService.Update(entry);
}
catch (Exception exception) {
Logger.Error(exception, "Error installing package {0}, version {1} from source {2}", packageId, version, sourceId);
_notifier.Error(T("Error installing package update."));
for (Exception scan = exception; scan != null; scan = scan.InnerException) {
_notifier.Error(T("{0}", scan.Message));
}
}
int reportId = CreateReport(T("Package Update"), T("Update of package {0} to version {1}", packageId, version));
return RedirectToAction(returnUrl, new { reportId });
}
private void CreateNotificationsFromReport(int reportId) {
// If we have notification in TempData, we don't need to display the
// report as notifications (i.e. the AppDomain hasn't been restarted)
// Note: This relies on an implementation detail of "Orchard.UI.Notify.NotifyFilter"
if (TempData["messages"] != null)
return;
var report = _reportsManager.Get(reportId);
if (report == null)
return;
if (report.Entries.Any()) {
_notifier.Information(T("Application has been restarted. The following notifications originate from report #{0}:", reportId));
}
foreach(var entry in report.Entries) {
switch(entry.Type) {
case ReportEntryType.Information:
_notifier.Add(NotifyType.Information, T(entry.Message));
break;
case ReportEntryType.Warning:
_notifier.Add(NotifyType.Warning, T(entry.Message));
break;
case ReportEntryType.Error:
default:
_notifier.Add(NotifyType.Error, T(entry.Message));
break;
}
}
}
private int CreateReport(LocalizedString activityName, LocalizedString title) {
// Create a persistent report with all notifications, in case the application needs to be restarted
const string reportKey = "PackageManager";
int reportId = _reportsCoordinator.Register(reportKey, activityName.Text, title.Text);
foreach(var notifyEntry in _notifier.List()) {
switch (notifyEntry.Type) {
case NotifyType.Information:
_reportsCoordinator.Add(reportKey, ReportEntryType.Information, notifyEntry.Message.Text);
break;
case NotifyType.Warning:
_reportsCoordinator.Add(reportKey, ReportEntryType.Warning, notifyEntry.Message.Text);
break;
case NotifyType.Error:
default:
_reportsCoordinator.Add(reportKey, ReportEntryType.Error, notifyEntry.Message.Text);
break;
}
}
return reportId;
}
}
}

View File

@@ -1,29 +1,23 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Orchard.Environment.Extensions;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.UI.Notify;
namespace Orchard.Packaging.Services {
public interface IFolderUpdater : IDependency {
void Backup(DirectoryInfo existingFolder, DirectoryInfo backupfolder);
void Update(DirectoryInfo destinationFolder, DirectoryInfo newFolder);
}
[OrchardFeature("Gallery.Updates")]
[OrchardFeature("PackagingServices")]
public class FolderUpdater : IFolderUpdater {
private readonly INotifier _notifier;
public class FolderContent {
public DirectoryInfo Folder { get; set; }
public IEnumerable<string> Files { get; set; }
}
public FolderUpdater(INotifier notifier) {
_notifier = notifier;
public FolderUpdater() {
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
}
@@ -35,33 +29,6 @@ namespace Orchard.Packaging.Services {
CopyFolder(GetFolderContent(existingFolder), backupfolder);
}
public void Update(DirectoryInfo destinationFolder, DirectoryInfo newFolder) {
var destinationContent = GetFolderContent(destinationFolder);
var newContent = GetFolderContent(newFolder);
Update(destinationContent, newContent);
}
private void Update(FolderContent destinationContent, FolderContent newContent) {
// Copy files from new folder to existing folder
foreach (var file in newContent.Files) {
CopyFile(newContent.Folder, file, destinationContent.Folder);
}
// Delete files that are in the existing folder but not in the new folder
foreach (var file in destinationContent.Files.Except(newContent.Files, StringComparer.OrdinalIgnoreCase)) {
var fileToDelete = new FileInfo(Path.Combine(destinationContent.Folder.FullName, file));
try {
fileToDelete.Delete();
}
catch (Exception exception) {
for (Exception scan = exception; scan != null; scan = scan.InnerException) {
_notifier.Warning(T("Unable to delete file \"{0}\": {1}", fileToDelete.FullName, scan.Message));
}
}
}
}
private void CopyFolder(FolderContent source, DirectoryInfo dest) {
foreach (var file in source.Files) {
CopyFile(source.Folder, file, dest);

View File

@@ -5,12 +5,44 @@ using Orchard.Packaging.GalleryServer;
using Orchard.Packaging.Models;
namespace Orchard.Packaging.Services {
/// <summary>
/// Responsible for managing package sources and getting the list of packages from it.
/// </summary>
public interface IPackagingSourceManager : IDependency {
/// <summary>
/// Gets the different feed sources.
/// </summary>
/// <returns>The feeds.</returns>
IEnumerable<PackagingSource> GetSources();
void AddSource(string feedTitle, string feedUrl);
/// <summary>
/// Adds a new feed sources.
/// </summary>
/// <param name="feedTitle">The feed title.</param>
/// <param name="feedUrl">The feed url.</param>
/// <returns>The feed identifier.</returns>
int AddSource(string feedTitle, string feedUrl);
/// <summary>
/// Removes a feed source.
/// </summary>
/// <param name="id">The feed identifier.</param>
void RemoveSource(int id);
/// <summary>
/// Retrieves the list of extensions from a feed source.
/// </summary>
/// <param name="packagingSource">The packaging source from where to get the extensions.</param>
/// <param name="query">The optional query to retrieve the extensions.</param>
/// <returns>The list of extensions.</returns>
IEnumerable<PackagingEntry> GetExtensionList(PackagingSource packagingSource = null, Func<IQueryable<PublishedPackage>, IQueryable<PublishedPackage>> query = null);
/// <summary>
/// Retrieves the number of extensions from a feed source.
/// </summary>
/// <param name="packagingSource">The packaging source from where to get the extensions.</param>
/// <param name="query">The optional query to retrieve the extensions.</param>
/// <returns>The number of extensions from a feed source.</returns>
int GetExtensionCount(PackagingSource packagingSource = null, Func<IQueryable<PublishedPackage>, IQueryable<PublishedPackage>> query = null);
}
}

View File

@@ -183,7 +183,6 @@ namespace Orchard.Packaging.Services {
context.Builder.Files.Add(file);
}
private static void EndPackage(CreateContext context) {
context.Builder.Save(context.Stream);
}
@@ -203,7 +202,6 @@ namespace Orchard.Packaging.Services {
#endregion
#region Nested type: CreateContext
private class VirtualPackageFile : IPackageFile {
@@ -229,6 +227,5 @@ namespace Orchard.Packaging.Services {
}
#endregion
}
}

View File

@@ -4,6 +4,7 @@ using System.Web.Hosting;
using NuGet;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.VirtualPath;
using Orchard.Localization;
using Orchard.Packaging.Models;
using Orchard.UI.Notify;
@@ -16,12 +17,20 @@ namespace Orchard.Packaging.Services {
private const string SolutionFilename = "Orchard.sln";
private readonly INotifier _notifier;
private readonly IVirtualPathProvider _virtualPathProvider;
private readonly IExtensionManager _extensionManager;
private readonly IFolderUpdater _folderUpdater;
public PackageInstaller(
INotifier notifier,
IVirtualPathProvider virtualPathProvider,
IExtensionManager extensionManager,
IFolderUpdater folderUpdater) {
public PackageInstaller(INotifier notifier,
IExtensionManager extensionManager) {
_notifier = notifier;
_virtualPathProvider = virtualPathProvider;
_extensionManager = extensionManager;
_folderUpdater = folderUpdater;
T = NullLocalizer.Instance;
}
@@ -35,21 +44,53 @@ namespace Orchard.Packaging.Services {
// gets an IPackage instance from the repository
var packageVersion = String.IsNullOrEmpty(version) ? null : new Version(version);
var package = packageRepository.FindPackage(packageId, packageVersion);
if (package == null)
{
if (package == null) {
throw new ArgumentException(T("The specified package could not be found, id:{0} version:{1}", packageId, String.IsNullOrEmpty(version) ? T("No version").Text : version).Text);
}
return Install(package, packageRepository, location, applicationPath);
return InstallPackage(package, packageRepository, location, applicationPath);
}
public PackageInfo Install(IPackage package, string location, string applicationPath) {
// instantiates the appropriate package repository
IPackageRepository packageRepository = PackageRepositoryFactory.Default.CreateRepository(new PackageSource(location, "Default"));
return Install(package, packageRepository, location, applicationPath);
return InstallPackage(package, packageRepository, location, applicationPath);
}
protected PackageInfo Install(IPackage package, IPackageRepository packageRepository, string location, string applicationPath) {
protected PackageInfo InstallPackage(IPackage package, IPackageRepository packageRepository, string location, string applicationPath) {
InstallContext context = new InstallContext { Package = package };
bool previousInstalled;
// 1. See if extension was previous installed and backup its folder if so
try {
previousInstalled = BackupExtensionFolder(context.ExtensionFolder, context.ExtensionId);
}
catch (Exception exception) {
throw new OrchardException(T("Unable to backup existing local package directory."), exception);
}
if (previousInstalled) {
// 2. If extension is installed, need to un-install first
try {
UninstallExtensionIfNeeded(context);
}
catch (Exception exception) {
throw new OrchardException(T("Unable to un-install local package before updating."), exception);
}
}
return ExecuteInstall(package, packageRepository, location, applicationPath);
}
/// <summary>
/// Executes a package installation.
/// </summary>
/// <param name="package">The package to install.</param>
/// <param name="packageRepository">The repository for the package.</param>
/// <param name="sourceLocation">The source location.</param>
/// <param name="targetPath">The path where to install the package.</param>
/// <returns>The package information.</returns>
protected PackageInfo ExecuteInstall(IPackage package, IPackageRepository packageRepository, string sourceLocation, string targetPath) {
// this logger is used to render NuGet's log on the notifier
var logger = new NugetLogger(_notifier);
@@ -58,12 +99,12 @@ namespace Orchard.Packaging.Services {
// if we can access the parent directory, and the solution is inside, NuGet-install the package here
string solutionPath;
var installedPackagesPath = String.Empty;
if (TryGetSolutionPath(applicationPath, out solutionPath)) {
if (TryGetSolutionPath(targetPath, out solutionPath)) {
installedPackagesPath = Path.Combine(solutionPath, PackagesPath);
try {
var packageManager = new NuGetPackageManager(
packageRepository,
new DefaultPackagePathResolver(location),
new DefaultPackagePathResolver(sourceLocation),
new PhysicalFileSystem(installedPackagesPath) {Logger = logger}
) {Logger = logger};
@@ -80,10 +121,10 @@ namespace Orchard.Packaging.Services {
? new LocalPackageRepository(installedPackagesPath)
: packageRepository;
var project = new FileBasedProjectSystem(applicationPath) { Logger = logger };
var project = new FileBasedProjectSystem(targetPath) { Logger = logger };
var projectManager = new ProjectManager(
sourceRepository, // source repository for the package to install
new DefaultPackagePathResolver(applicationPath),
new DefaultPackagePathResolver(targetPath),
project,
new ExtensionReferenceRepository(project, sourceRepository, _extensionManager)
) { Logger = logger };
@@ -95,18 +136,23 @@ namespace Orchard.Packaging.Services {
ExtensionName = package.Title ?? package.Id,
ExtensionVersion = package.Version.ToString(),
ExtensionType = package.Id.StartsWith(PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Theme)) ? DefaultExtensionTypes.Theme : DefaultExtensionTypes.Module,
ExtensionPath = applicationPath
ExtensionPath = targetPath
};
}
/// <summary>
/// Uninstalls a package.
/// </summary>
/// <param name="packageId">The package identifier for the package to be uninstalled.</param>
/// <param name="applicationPath">The application path.</param>
public void Uninstall(string packageId, string applicationPath) {
string solutionPath;
string extensionFullPath = string.Empty;
if (packageId.StartsWith(PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Theme))) {
extensionFullPath = HostingEnvironment.MapPath("~/Themes/" + packageId.Substring(PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Theme).Length));
extensionFullPath = _virtualPathProvider.MapPath("~/Themes/" + packageId.Substring(PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Theme).Length));
} else if (packageId.StartsWith(PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Module))) {
extensionFullPath = HostingEnvironment.MapPath("~/Modules/" + packageId.Substring(PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Module).Length));
extensionFullPath = _virtualPathProvider.MapPath("~/Modules/" + packageId.Substring(PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Module).Length));
}
if (string.IsNullOrEmpty(extensionFullPath) ||
@@ -155,7 +201,7 @@ namespace Orchard.Packaging.Services {
}
// If the package was not installed through nuget we still need to try to uninstall it by removing its directory
if(Directory.Exists(extensionFullPath)) {
if (Directory.Exists(extensionFullPath)) {
Directory.Delete(extensionFullPath, true);
}
}
@@ -172,5 +218,64 @@ namespace Orchard.Packaging.Services {
return false;
}
}
private bool BackupExtensionFolder(string extensionFolder, string extensionId) {
var source = new DirectoryInfo(_virtualPathProvider.MapPath(_virtualPathProvider.Combine("~", extensionFolder, extensionId)));
if (source.Exists) {
var tempPath = _virtualPathProvider.Combine("~", extensionFolder, "_Backup", extensionId);
string localTempPath = null;
for (int i = 0; i < 1000; i++) {
localTempPath = _virtualPathProvider.MapPath(tempPath) + (i == 0 ? "" : "." + i.ToString());
if (!Directory.Exists(localTempPath)) {
Directory.CreateDirectory(localTempPath);
break;
}
localTempPath = null;
}
if (localTempPath == null) {
throw new OrchardException(T("Backup folder {0} has too many backups subfolder (limit is 1,000)", tempPath));
}
var backupFolder = new DirectoryInfo(localTempPath);
_folderUpdater.Backup(source, backupFolder);
_notifier.Information(T("Successfully backed up local package to local folder \"{0}\"", backupFolder));
return true;
}
return false;
}
private void UninstallExtensionIfNeeded(InstallContext context) {
// Nuget requires to un-install the currently installed packages if the new
// package is the same version or an older version
try {
Uninstall(context.Package.Id, _virtualPathProvider.MapPath("~\\"));
_notifier.Information(T("Successfully un-installed local package {0}", context.ExtensionId));
}
catch {}
}
internal class InstallContext {
public IPackage Package { get; set; }
public bool IsTheme {
get {
return Package.Id.StartsWith(PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Theme));
}
}
public string ExtensionFolder {
get { return IsTheme ? "Themes" : "Modules"; }
}
public string ExtensionId {
get {
return IsTheme ?
Package.Id.Substring(PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Theme).Length) :
Package.Id.Substring(PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Module).Length);
}
}
}
}
}

View File

@@ -1,23 +1,17 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Orchard.Caching;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.VirtualPath;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Packaging.Models;
using Orchard.Services;
using Orchard.UI.Notify;
namespace Orchard.Packaging.Services {
public interface IPackageUpdateService : IDependency {
PackagesStatusResult GetPackagesStatus(IEnumerable<PackagingSource> sources);
void TriggerRefresh();
void Update(PackagingEntry entry);
void Uninstall(string packageId);
}
[OrchardFeature("Gallery.Updates")]
@@ -27,30 +21,19 @@ namespace Orchard.Packaging.Services {
private readonly ICacheManager _cacheManager;
private readonly IClock _clock;
private readonly ISignals _signals;
private readonly INotifier _notifier;
private readonly IVirtualPathProvider _virtualPathProvider;
private readonly IPackageManager _packageManager;
private readonly IFolderUpdater _folderUpdater;
public PackageUpdateService(IPackagingSourceManager packagingSourceManager,
IExtensionManager extensionManager,
ICacheManager cacheManager,
IClock clock,
ISignals signals,
INotifier notifier,
IVirtualPathProvider virtualPathProvider,
IPackageManager packageManager,
IFolderUpdater folderUpdater) {
ISignals signals) {
_packagingSourceManager = packagingSourceManager;
_extensionManager = extensionManager;
_cacheManager = cacheManager;
_clock = clock;
_signals = signals;
_notifier = notifier;
_virtualPathProvider = virtualPathProvider;
_packageManager = packageManager;
_folderUpdater = folderUpdater;
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
}
@@ -129,133 +112,5 @@ namespace Orchard.Packaging.Services {
}
return entry;
}
public class UpdateContext {
public PackagingEntry PackagingEntry { get; set; }
public bool IsTheme {
get {
return PackagingEntry.PackageId.StartsWith(PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Theme));
}
}
public string ExtensionFolder {
get { return IsTheme ? "Themes" : "Modules"; }
}
public string ExtensionId {
get {
return IsTheme ?
PackagingEntry.PackageId.Substring(PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Theme).Length) :
PackagingEntry.PackageId.Substring(PackagingSourceManager.GetExtensionPrefix(DefaultExtensionTypes.Module).Length);
}
}
}
public void Update(PackagingEntry entry) {
var context = new UpdateContext { PackagingEntry = entry };
// 1. Backup extension folder
try {
BackupExtensionFolder(context.ExtensionFolder, context.ExtensionId);
}
catch (Exception exception) {
throw new OrchardException(T("Unable to backup existing local package directory."), exception);
}
// 2. If extension is installed, need to un-install first
try {
UninstallExtensionIfNeeded(context);
}
catch (Exception exception) {
throw new OrchardException(T("Unable to un-install local package before updating."), exception);
}
// 3. Install package from Gallery to temporary folder
DirectoryInfo newPackageFolder;
try {
newPackageFolder = InstallPackage(context);
}
catch (Exception exception) {
throw new OrchardException(T("Package installation failed."), exception);
}
// 4. Copy new package content to extension folder
try {
UpdateExtensionFolder(context, newPackageFolder);
}
catch (Exception exception) {
throw new OrchardException(T("Package update failed."), exception);
}
}
public void Uninstall(string packageId) {
var context = new UpdateContext { PackagingEntry = new PackagingEntry {PackageId = packageId} };
// Backup extension folder
try {
BackupExtensionFolder(context.ExtensionFolder, context.ExtensionId);
}
catch (Exception exception) {
throw new OrchardException(T("Unable to backup existing local package directory."), exception);
}
// Uninstall package from local folder
_packageManager.Uninstall(packageId, _virtualPathProvider.MapPath("~/"));
_notifier.Information(T("Successfully un-installed local package {0}", packageId));
}
private void BackupExtensionFolder(string extensionFolder, string extensionId) {
var tempPath = _virtualPathProvider.Combine("~", extensionFolder, "_Backup", extensionId);
string localTempPath = null;
for (int i = 0; i < 1000; i++) {
localTempPath = _virtualPathProvider.MapPath(tempPath) + (i == 0 ? "" : "." + i.ToString());
if (!Directory.Exists(localTempPath)) {
Directory.CreateDirectory(localTempPath);
break;
}
localTempPath = null;
}
if (localTempPath == null) {
throw new OrchardException(T("Backup folder {0} has too many backups subfolder (limit is 1,000)", tempPath));
}
var backupFolder = new DirectoryInfo(localTempPath);
var source = new DirectoryInfo(_virtualPathProvider.MapPath(_virtualPathProvider.Combine("~", extensionFolder, extensionId)));
_folderUpdater.Backup(source, backupFolder);
_notifier.Information(T("Successfully backed up local package to local folder \"{0}\"", backupFolder));
}
private void UninstallExtensionIfNeeded(UpdateContext context) {
// Nuget requires to un-install the currently installed packages if the new
// package is the same version or an older version
var extension = _extensionManager
.AvailableExtensions()
.Where(e => e.Id == context.ExtensionId && new Version(e.Version) >= new Version(context.PackagingEntry.Version))
.FirstOrDefault();
if (extension == null)
return;
_packageManager.Uninstall(context.PackagingEntry.PackageId, _virtualPathProvider.MapPath("~/"));
_notifier.Information(T("Successfully un-installed local package {0}", context.ExtensionId));
}
private DirectoryInfo InstallPackage(UpdateContext context) {
var tempPath = _virtualPathProvider.Combine("~", context.ExtensionFolder, "_Updates");
var destPath = _virtualPathProvider.Combine(tempPath, context.ExtensionFolder, context.ExtensionId);
var localDestPath = (_virtualPathProvider.MapPath(destPath));
if (Directory.Exists(localDestPath)) {
Directory.Delete(localDestPath, true);
}
_packageManager.Install(context.PackagingEntry.PackageId, context.PackagingEntry.Version, context.PackagingEntry.Source.FeedUrl, _virtualPathProvider.MapPath(tempPath));
return new DirectoryInfo(localDestPath);
}
private void UpdateExtensionFolder(UpdateContext context, DirectoryInfo newPackageFolder) {
var extensionPath = _virtualPathProvider.Combine("~", context.ExtensionFolder, context.ExtensionId);
var extensionFolder = new DirectoryInfo(_virtualPathProvider.MapPath(extensionPath));
_folderUpdater.Update(extensionFolder, newPackageFolder);
_notifier.Information(T("Successfully installed package \"{0}\" to local folder \"{1}\"", context.ExtensionId, extensionFolder));
}
}
}

View File

@@ -8,6 +8,9 @@ using Orchard.Packaging.GalleryServer;
using Orchard.Packaging.Models;
namespace Orchard.Packaging.Services {
/// <summary>
/// Responsible for managing package sources and getting the list of packages from it.
/// </summary>
[OrchardFeature("PackagingServices")]
public class PackagingSourceManager : IPackagingSourceManager {
public static string GetExtensionPrefix(string extensionType) {
@@ -25,15 +28,32 @@ namespace Orchard.Packaging.Services {
#region IPackagingSourceManager Members
/// <summary>
/// Gets the different feed sources.
/// </summary>
/// <returns>The feeds.</returns>
public IEnumerable<PackagingSource> GetSources() {
return _packagingSourceRecordRepository.Table.ToList();
}
public void AddSource(string feedTitle, string feedUrl) {
var packagingSource = new PackagingSource {FeedTitle = feedTitle, FeedUrl = feedUrl};
/// <summary>
/// Adds a new feed sources.
/// </summary>
/// <param name="feedTitle">The feed title.</param>
/// <param name="feedUrl">The feed url.</param>
/// <returns>The feed identifier.</returns>
public int AddSource(string feedTitle, string feedUrl) {
PackagingSource packagingSource = new PackagingSource { FeedTitle = feedTitle, FeedUrl = feedUrl };
_packagingSourceRecordRepository.Create(packagingSource);
return packagingSource.Id;
}
/// <summary>
/// Removes a feed source.
/// </summary>
/// <param name="id">The feed identifier.</param>
public void RemoveSource(int id) {
var packagingSource = _packagingSourceRecordRepository.Get(id);
if(packagingSource != null) {
@@ -41,6 +61,12 @@ namespace Orchard.Packaging.Services {
}
}
/// <summary>
/// Retrieves the list of extensions from a feed source.
/// </summary>
/// <param name="packagingSource">The packaging source from where to get the extensions.</param>
/// <param name="query">The optional query to retrieve the extensions.</param>
/// <returns>The list of extensions.</returns>
public IEnumerable<PackagingEntry> GetExtensionList(PackagingSource packagingSource = null, Func<IQueryable<PublishedPackage>, IQueryable<PublishedPackage>> query = null) {
return (packagingSource == null ? GetSources() : new[] {packagingSource})
.SelectMany(
@@ -64,6 +90,12 @@ namespace Orchard.Packaging.Services {
);
}
/// <summary>
/// Retrieves the number of extensions from a feed source.
/// </summary>
/// <param name="packagingSource">The packaging source from where to get the extensions.</param>
/// <param name="query">The optional query to retrieve the extensions.</param>
/// <returns>The number of extensions from a feed source.</returns>
public int GetExtensionCount(PackagingSource packagingSource = null, Func<IQueryable<PublishedPackage>, IQueryable<PublishedPackage>> query = null) {
return (packagingSource == null ? GetSources() : new[] { packagingSource })
.Sum( source => {
@@ -79,6 +111,8 @@ namespace Orchard.Packaging.Services {
);
}
#endregion
private static PackagingEntry CreatePackageEntry(PublishedPackage package, PublishedScreenshot screenshot, PackagingSource source, Uri downloadUri) {
Uri baseUri = new Uri(string.Format("{0}://{1}:{2}/",
downloadUri.Scheme,
@@ -109,10 +143,8 @@ namespace Orchard.Packaging.Services {
protected static string GetAbsoluteUri(string url, Uri baseUri) {
Uri uri = null;
if (!string.IsNullOrEmpty(url))
{
if (!Uri.TryCreate(url, UriKind.Absolute, out uri))
{
if (!string.IsNullOrEmpty(url)) {
if (!Uri.TryCreate(url, UriKind.Absolute, out uri)) {
Uri.TryCreate(baseUri,
url,
out uri);
@@ -121,7 +153,5 @@ namespace Orchard.Packaging.Services {
return uri != null ? uri.ToString() : string.Empty;
}
#endregion
}
}

View File

@@ -15,19 +15,12 @@
@functions {
public string InstallAction(PackagingEntry package) {
return Url.Action("Install", "GalleryUpdates", new {
return Url.Action("InstallGallery", "PackagingServices", new {
area = "Orchard.Packaging",
packageId = package.PackageId,
version = package.Version,
sourceId = package.Source.Id,
returnUrl = "ModulesUpdates"
});
}
public string UninstallAction(PackagingEntry package) {
return Url.Action("Uninstall", "GalleryUpdates", new {
area = "Orchard.Packaging",
packageId = package.PackageId,
returnUrl = "ModulesUpdates"
redirectUrl = HttpContext.Current.Request.RawUrl
});
}
}

View File

@@ -15,19 +15,12 @@
@functions {
public string InstallAction(PackagingEntry package) {
return Url.Action("Install", "GalleryUpdates", new {
return Url.Action("InstallGallery", "PackagingServices", new {
area = "Orchard.Packaging",
packageId = package.PackageId,
version = package.Version,
sourceId = package.Source.Id,
returnUrl = "ThemesUpdates"
});
}
public string UninstallAction(PackagingEntry package) {
return Url.Action("Uninstall", "GalleryUpdates", new {
area = "Orchard.Packaging",
packageId = package.PackageId,
returnUrl = "ThemesUpdates"
redirectUrl = HttpContext.Current.Request.RawUrl
});
}
}