diff --git a/src/Orchard.Tests.Modules/Indexing/IndexingTaskExecutorTests.cs b/src/Orchard.Tests.Modules/Indexing/IndexingTaskExecutorTests.cs index 68e0b46fa..62772909c 100644 --- a/src/Orchard.Tests.Modules/Indexing/IndexingTaskExecutorTests.cs +++ b/src/Orchard.Tests.Modules/Indexing/IndexingTaskExecutorTests.cs @@ -54,6 +54,7 @@ namespace Orchard.Tests.Modules.Indexing { if (Directory.Exists(_basePath)) { Directory.Delete(_basePath, true); } + Directory.CreateDirectory(_basePath); _contentDefinitionManager = new Mock(); _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(ThingDriver.ContentTypeName)); @@ -278,6 +280,7 @@ namespace Orchard.Tests.Modules.Indexing { }); } } + #endregion } } diff --git a/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj b/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj index 9f20e9dcc..dfacdb755 100644 --- a/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj +++ b/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj @@ -149,6 +149,8 @@ + + diff --git a/src/Orchard.Tests.Modules/Packaging/Services/FolderUpdaterTests.cs b/src/Orchard.Tests.Modules/Packaging/Services/FolderUpdaterTests.cs new file mode 100644 index 000000000..632ad6df0 --- /dev/null +++ b/src/Orchard.Tests.Modules/Packaging/Services/FolderUpdaterTests.cs @@ -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().As(); + builder.RegisterInstance(new Mock().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(); + + 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); + } + } +} diff --git a/src/Orchard.Tests.Modules/Packaging/Services/PackageInstallerTests.cs b/src/Orchard.Tests.Modules/Packaging/Services/PackageInstallerTests.cs new file mode 100644 index 000000000..59a97b051 --- /dev/null +++ b/src/Orchard.Tests.Modules/Packaging/Services/PackageInstallerTests.cs @@ -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 _mockedVirtualPathProvider; + + protected override void Register(ContainerBuilder builder) { + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterInstance(new Mock().Object); + builder.RegisterType().As(); + + _mockedVirtualPathProvider = new Mock(); + builder.RegisterInstance(_mockedVirtualPathProvider.Object).As(); + builder.RegisterType().As(); + builder.RegisterType().As() + .As().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(); + 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(); + 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(); + + _mockedVirtualPathProvider.Setup(v => v.MapPath(It.IsAny())) + .Returns(path => Path.Combine(_basePath, path.Replace("~\\", ""))); + + _mockedVirtualPathProvider.Setup(v => v.Combine(It.IsAny())) + .Returns(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(); + 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(); + + _mockedVirtualPathProvider.Setup(v => v.MapPath(It.IsAny())) + .Returns(path => Path.Combine(_basePath, path.Replace("~\\", "").Replace("~/", ""))); + + _mockedVirtualPathProvider.Setup(v => v.Combine(It.IsAny())) + .Returns(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(); + 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(); + + _mockedVirtualPathProvider.Setup(v => v.MapPath(It.IsAny())) + .Returns(path => Path.Combine(_basePath, path.Replace("~\\", "").Replace("~/", ""))); + + _mockedVirtualPathProvider.Setup(v => v.Combine(It.IsAny())) + .Returns(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); + } + } +} diff --git a/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ModuleRecipeHandlerTest.cs b/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ModuleRecipeHandlerTest.cs index 3358c7189..e128fbd5b 100644 --- a/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ModuleRecipeHandlerTest.cs +++ b/src/Orchard.Tests.Modules/Recipes/RecipeHandlers/ModuleRecipeHandlerTest.cs @@ -118,7 +118,7 @@ Features: return Enumerable.Empty(); } - public void AddSource(string feedTitle, string feedUrl) { + public int AddSource(string feedTitle, string feedUrl) { throw new NotImplementedException(); } diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/GalleryUpdatesController.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/GalleryUpdatesController.cs index 45a6a388d..1b7566d2b 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/GalleryUpdatesController.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/GalleryUpdatesController.cs @@ -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() }); @@ -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; - } } } diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/FolderUpdater.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Services/FolderUpdater.cs index 8ab765f32..d2d6546d4 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/FolderUpdater.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Services/FolderUpdater.cs @@ -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 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); diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/IPackagingSourceManager.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Services/IPackagingSourceManager.cs index 6ad7baf67..044b170f1 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/IPackagingSourceManager.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Services/IPackagingSourceManager.cs @@ -5,12 +5,44 @@ using Orchard.Packaging.GalleryServer; using Orchard.Packaging.Models; namespace Orchard.Packaging.Services { + /// + /// Responsible for managing package sources and getting the list of packages from it. + /// public interface IPackagingSourceManager : IDependency { + /// + /// Gets the different feed sources. + /// + /// The feeds. IEnumerable GetSources(); - void AddSource(string feedTitle, string feedUrl); + + /// + /// Adds a new feed sources. + /// + /// The feed title. + /// The feed url. + /// The feed identifier. + int AddSource(string feedTitle, string feedUrl); + + /// + /// Removes a feed source. + /// + /// The feed identifier. void RemoveSource(int id); + /// + /// Retrieves the list of extensions from a feed source. + /// + /// The packaging source from where to get the extensions. + /// The optional query to retrieve the extensions. + /// The list of extensions. IEnumerable GetExtensionList(PackagingSource packagingSource = null, Func, IQueryable> query = null); + + /// + /// Retrieves the number of extensions from a feed source. + /// + /// The packaging source from where to get the extensions. + /// The optional query to retrieve the extensions. + /// The number of extensions from a feed source. int GetExtensionCount(PackagingSource packagingSource = null, Func, IQueryable> query = null); } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageBuilder.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageBuilder.cs index 496f38e90..ce9429c01 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageBuilder.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageBuilder.cs @@ -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 - } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageInstaller.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageInstaller.cs index 42c073892..f565a5e73 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageInstaller.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageInstaller.cs @@ -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); + } + + /// + /// Executes a package installation. + /// + /// The package to install. + /// The repository for the package. + /// The source location. + /// The path where to install the package. + /// The package information. + 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 }; } + /// + /// Uninstalls a package. + /// + /// The package identifier for the package to be uninstalled. + /// The application path. 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); + } + } + } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageUpdateManager.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageUpdateManager.cs index c50f15887..7580c5fcd 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageUpdateManager.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageUpdateManager.cs @@ -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 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)); - } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingSourceManager.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingSourceManager.cs index bf136f2ee..d844fd84b 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingSourceManager.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingSourceManager.cs @@ -8,6 +8,9 @@ using Orchard.Packaging.GalleryServer; using Orchard.Packaging.Models; namespace Orchard.Packaging.Services { + /// + /// Responsible for managing package sources and getting the list of packages from it. + /// [OrchardFeature("PackagingServices")] public class PackagingSourceManager : IPackagingSourceManager { public static string GetExtensionPrefix(string extensionType) { @@ -25,15 +28,32 @@ namespace Orchard.Packaging.Services { #region IPackagingSourceManager Members + /// + /// Gets the different feed sources. + /// + /// The feeds. public IEnumerable GetSources() { return _packagingSourceRecordRepository.Table.ToList(); } - public void AddSource(string feedTitle, string feedUrl) { - var packagingSource = new PackagingSource {FeedTitle = feedTitle, FeedUrl = feedUrl}; + /// + /// Adds a new feed sources. + /// + /// The feed title. + /// The feed url. + /// The feed identifier. + public int AddSource(string feedTitle, string feedUrl) { + PackagingSource packagingSource = new PackagingSource { FeedTitle = feedTitle, FeedUrl = feedUrl }; + _packagingSourceRecordRepository.Create(packagingSource); + + return packagingSource.Id; } + /// + /// Removes a feed source. + /// + /// The feed identifier. public void RemoveSource(int id) { var packagingSource = _packagingSourceRecordRepository.Get(id); if(packagingSource != null) { @@ -41,6 +61,12 @@ namespace Orchard.Packaging.Services { } } + /// + /// Retrieves the list of extensions from a feed source. + /// + /// The packaging source from where to get the extensions. + /// The optional query to retrieve the extensions. + /// The list of extensions. public IEnumerable GetExtensionList(PackagingSource packagingSource = null, Func, IQueryable> query = null) { return (packagingSource == null ? GetSources() : new[] {packagingSource}) .SelectMany( @@ -64,6 +90,12 @@ namespace Orchard.Packaging.Services { ); } + /// + /// Retrieves the number of extensions from a feed source. + /// + /// The packaging source from where to get the extensions. + /// The optional query to retrieve the extensions. + /// The number of extensions from a feed source. public int GetExtensionCount(PackagingSource packagingSource = null, Func, IQueryable> 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 } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Views/GalleryUpdates/ModulesUpdates.cshtml b/src/Orchard.Web/Modules/Orchard.Packaging/Views/GalleryUpdates/ModulesUpdates.cshtml index 8f0f6da8a..746fddabd 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Views/GalleryUpdates/ModulesUpdates.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Views/GalleryUpdates/ModulesUpdates.cshtml @@ -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 }); } } diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Views/GalleryUpdates/ThemesUpdates.cshtml b/src/Orchard.Web/Modules/Orchard.Packaging/Views/GalleryUpdates/ThemesUpdates.cshtml index 282883079..887dc494f 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Views/GalleryUpdates/ThemesUpdates.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Views/GalleryUpdates/ThemesUpdates.cshtml @@ -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 }); } }