From 6050e585be33280b5258c838adb27126f5e2ec3e Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Fri, 18 Feb 2011 08:21:19 -0800 Subject: [PATCH] Integrating package manager for package updates. --HG-- branch : dev rename : src/Orchard.Web/Modules/Orchard.Packaging/Services/NugetLogger.cs => src/Orchard.Web/Modules/Orchard.Packaging/Models/NugetLogger.cs rename : src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageData.cs => src/Orchard.Web/Modules/Orchard.Packaging/Models/PackageData.cs rename : src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingEntry.cs => src/Orchard.Web/Modules/Orchard.Packaging/Models/PackagingEntry.cs --- .../Stubs/StubReportsCoordinator.cs | 4 +- .../Controllers/AdminController.cs | 4 +- .../Services/IModuleService.cs | 2 +- .../Orchard.Modules/Services/ModuleService.cs | 4 +- .../Orchard.PackageManager/AdminMenu.cs | 48 +++ .../Controllers/AdminController.cs | 205 +++++++++++++ .../Modules/Orchard.PackageManager/Module.txt | 13 + .../Orchard.PackageManager.csproj | 133 ++++++++ .../Properties/AssemblyInfo.cs | 34 +++ .../Services/BackgroundPackageUpdateStatus.cs | 9 + .../Services/BackgroundPackageUpdateTask.cs | 32 ++ .../Services/FolderUpdater.cs | 112 +++++++ .../Services/PackageUpdateManager.cs | 285 ++++++++++++++++++ .../ViewModels/PackageList.cs | 8 + .../Views/Admin/ModulesUpdate.cshtml | 76 +++++ .../Views/Admin/ThemesUpdate.cshtml | 76 +++++ .../Orchard.PackageManager/Views/Web.config | 41 +++ .../Modules/Orchard.PackageManager/Web.config | 39 +++ .../Modules/Orchard.Packaging/AdminMenu.cs | 1 - .../Controllers/GalleryController.cs | 1 - .../{Services => Models}/NugetLogger.cs | 2 +- .../{Services => Models}/PackageData.cs | 2 +- .../{Services => Models}/PackagingEntry.cs | 3 +- .../Orchard.Packaging.csproj | 6 +- .../Services/IPackageManager.cs | 1 + .../Services/IPackagingSourceManager.cs | 1 - .../Services/PackageInstaller.cs | 1 + .../Services/PackageManager.cs | 1 + .../Services/PackagingSourceManager.cs | 1 - .../Controllers/AdminController.cs | 2 +- .../Orchard.Themes/Services/IThemeService.cs | 2 +- .../Orchard.Themes/Services/ThemeService.cs | 4 +- src/Orchard.sln | 13 + .../Reports/Services/IReportsCoordinator.cs | 2 +- .../Reports/Services/ReportsCoordinator.cs | 6 +- 35 files changed, 1149 insertions(+), 25 deletions(-) create mode 100644 src/Orchard.Web/Modules/Orchard.PackageManager/AdminMenu.cs create mode 100644 src/Orchard.Web/Modules/Orchard.PackageManager/Controllers/AdminController.cs create mode 100644 src/Orchard.Web/Modules/Orchard.PackageManager/Module.txt create mode 100644 src/Orchard.Web/Modules/Orchard.PackageManager/Orchard.PackageManager.csproj create mode 100644 src/Orchard.Web/Modules/Orchard.PackageManager/Properties/AssemblyInfo.cs create mode 100644 src/Orchard.Web/Modules/Orchard.PackageManager/Services/BackgroundPackageUpdateStatus.cs create mode 100644 src/Orchard.Web/Modules/Orchard.PackageManager/Services/BackgroundPackageUpdateTask.cs create mode 100644 src/Orchard.Web/Modules/Orchard.PackageManager/Services/FolderUpdater.cs create mode 100644 src/Orchard.Web/Modules/Orchard.PackageManager/Services/PackageUpdateManager.cs create mode 100644 src/Orchard.Web/Modules/Orchard.PackageManager/ViewModels/PackageList.cs create mode 100644 src/Orchard.Web/Modules/Orchard.PackageManager/Views/Admin/ModulesUpdate.cshtml create mode 100644 src/Orchard.Web/Modules/Orchard.PackageManager/Views/Admin/ThemesUpdate.cshtml create mode 100644 src/Orchard.Web/Modules/Orchard.PackageManager/Views/Web.config create mode 100644 src/Orchard.Web/Modules/Orchard.PackageManager/Web.config rename src/Orchard.Web/Modules/Orchard.Packaging/{Services => Models}/NugetLogger.cs (92%) rename src/Orchard.Web/Modules/Orchard.Packaging/{Services => Models}/PackageData.cs (84%) rename src/Orchard.Web/Modules/Orchard.Packaging/{Services => Models}/PackagingEntry.cs (89%) diff --git a/src/Orchard.Tests/Stubs/StubReportsCoordinator.cs b/src/Orchard.Tests/Stubs/StubReportsCoordinator.cs index 01c4792ec..0621a0c5d 100644 --- a/src/Orchard.Tests/Stubs/StubReportsCoordinator.cs +++ b/src/Orchard.Tests/Stubs/StubReportsCoordinator.cs @@ -7,8 +7,8 @@ namespace Orchard.Tests.Stubs { } - public void Register(string reportKey, string activityName, string title) { - + public int Register(string reportKey, string activityName, string title) { + return 0; } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Modules/Controllers/AdminController.cs index dba9dc7c9..28846a644 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.Modules/Controllers/AdminController.cs @@ -56,7 +56,7 @@ namespace Orchard.Modules.Controllers { IEnumerable modules = _extensionManager.AvailableExtensions() .Where(x => DefaultExtensionTypes.IsModule(x.ExtensionType)) .Select(extensionDescriptor => new Module(extensionDescriptor) { - IsRecentlyInstalled = _moduleService.UpdateIsRecentlyInstalled(extensionDescriptor) + IsRecentlyInstalled = _moduleService.IsRecentlyInstalled(extensionDescriptor) }); return View(new ModulesIndexViewModel { @@ -76,7 +76,7 @@ namespace Orchard.Modules.Controllers { .Select(f => new ModuleFeature { Descriptor = f, IsEnabled = _shellDescriptor.Features.Any(sf => sf.Name == f.Id), - IsRecentlyInstalled = _moduleService.UpdateIsRecentlyInstalled(f.Extension), + IsRecentlyInstalled = _moduleService.IsRecentlyInstalled(f.Extension), NeedsUpdate = featuresThatNeedUpdate.Contains(f.Id) }); diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Services/IModuleService.cs b/src/Orchard.Web/Modules/Orchard.Modules/Services/IModuleService.cs index cc9b35a1b..85e2639d7 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Services/IModuleService.cs +++ b/src/Orchard.Web/Modules/Orchard.Modules/Services/IModuleService.cs @@ -7,6 +7,6 @@ namespace Orchard.Modules.Services { void EnableFeatures(IEnumerable featureNames, bool force); void DisableFeatures(IEnumerable featureNames); void DisableFeatures(IEnumerable featureNames, bool force); - bool UpdateIsRecentlyInstalled(ExtensionDescriptor module); + bool IsRecentlyInstalled(ExtensionDescriptor module); } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Services/ModuleService.cs b/src/Orchard.Web/Modules/Orchard.Modules/Services/ModuleService.cs index dffc75c62..dc25775f8 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Services/ModuleService.cs +++ b/src/Orchard.Web/Modules/Orchard.Modules/Services/ModuleService.cs @@ -95,10 +95,10 @@ namespace Orchard.Modules.Services { } /// - /// Updates the recently installed flag by using the project's last written time. + /// Determines if a module was recently installed by using the project's last written time. /// /// The extension descriptor. - public bool UpdateIsRecentlyInstalled(ExtensionDescriptor descriptor) { + public bool IsRecentlyInstalled(ExtensionDescriptor descriptor) { string projectFile = GetManifestPath(descriptor); if (!string.IsNullOrEmpty(projectFile)) { // If project file was modified less than 24 hours ago, the module was recently deployed diff --git a/src/Orchard.Web/Modules/Orchard.PackageManager/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.PackageManager/AdminMenu.cs new file mode 100644 index 000000000..d9e169746 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.PackageManager/AdminMenu.cs @@ -0,0 +1,48 @@ +using System.Linq; +using Orchard.Environment.Extensions.Models; +using Orchard.Localization; +using Orchard.PackageManager.Services; +using Orchard.Security; +using Orchard.UI.Navigation; + +namespace Orchard.PackageManager { + public class AdminMenu : INavigationProvider { + private readonly IBackgroundPackageUpdateStatus _backgroundPackageUpdateStatus; + + public AdminMenu(IBackgroundPackageUpdateStatus backgroundPackageUpdateStatus) { + _backgroundPackageUpdateStatus = backgroundPackageUpdateStatus; + } + + public Localizer T { get; set; } + public string MenuName { get { return "admin"; } } + + public void GetNavigation(NavigationBuilder builder) { + int modulesUpdateCount = GetUpdateCount(DefaultExtensionTypes.Module); + LocalizedString modulesCaption = (modulesUpdateCount == 0 ? T("Updates") : T("Updates ({0})", modulesUpdateCount)); + + int themesUpdateCount = GetUpdateCount(DefaultExtensionTypes.Module); + LocalizedString themesCaption = (themesUpdateCount == 0 ? T("Updates") : T("Updates ({0})", themesUpdateCount)); + + builder.Add(T("Modules"), "20", menu => menu + .Add(modulesCaption, "30.0", item => item.Action("ModulesUpdates", "Admin", new { area = "Orchard.PackageManager" }) + .Permission(StandardPermissions.SiteOwner).LocalNav())); + + builder.Add(T("Themes"), "25", menu => menu + .Add(themesCaption, "30.0", item => item.Action("ThemesUpdates", "Admin", new { area = "Orchard.PackageManager" }) + .Permission(StandardPermissions.SiteOwner).LocalNav())); + } + + private int GetUpdateCount(string extensionType) { + try { + // Admin menu should never block, so simply return the result from the background task + return _backgroundPackageUpdateStatus.Value == null ? + 0 : + _backgroundPackageUpdateStatus.Value.Entries.Where(updatePackageEntry => + updatePackageEntry.NewVersionToInstall != null && + updatePackageEntry.ExtensionsDescriptor.ExtensionType.Equals(extensionType)).Count(); + } catch { + return 0; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.PackageManager/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.PackageManager/Controllers/AdminController.cs new file mode 100644 index 000000000..6caaeccd2 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.PackageManager/Controllers/AdminController.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using Orchard.Environment.Extensions.Models; +using Orchard.Localization; +using Orchard.Logging; +using Orchard.PackageManager.Services; +using Orchard.PackageManager.ViewModels; +using Orchard.Packaging.Services; +using Orchard.Reports; +using Orchard.Reports.Services; +using Orchard.Security; +using Orchard.UI.Notify; + +namespace Orchard.PackageManager.Controllers { + public class AdminController : Controller { + 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 AdminController(IOrchardServices services, + IPackagingSourceManager packagingSourceManager, + INotifier notifier, + IPackageUpdateService packageUpdateService, + IBackgroundPackageUpdateStatus backgroundPackageUpdateStatus, + IReportsCoordinator reportsCoordinator, + IReportsManager reportsManager) { + + _packagingSourceManager = packagingSourceManager; + _notifier = notifier; + _packageUpdateService = packageUpdateService; + _backgroundPackageUpdateStatus = backgroundPackageUpdateStatus; + _reportsCoordinator = reportsCoordinator; + _reportsManager = reportsManager; + Services = services; + + T = NullLocalizer.Instance; + Logger = NullLogger.Instance; + } + + public IOrchardServices Services { get; private set; } + public Localizer T { get; set; } + public ILogger Logger { get; set; } + + public ActionResult ThemesUpdates(int? reportId) { + return PackageUpdate("ThemesUpdate", DefaultExtensionTypes.Theme, reportId); + } + + public ActionResult ModulesUpdates(int? reportId) { + return PackageUpdate("ModulesUpdate", DefaultExtensionTypes.Theme, reportId); + } + + private ActionResult PackageUpdate(string view, string extensionType, int? reportId) { + if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to install packages"))) + return new HttpUnauthorizedResult(); + + if (reportId != null) + CreateNotificationsFromReport(reportId.Value); + + if (!_packagingSourceManager.GetSources().Any()) { + Services.Notifier.Error(T("No Gallery feed configured")); + return View(view, new PackageList { Entries = new List() }); + } + + // Get status from background task state or directly + _backgroundPackageUpdateStatus.Value = + _backgroundPackageUpdateStatus.Value ?? + _packageUpdateService.GetPackagesStatus(_packagingSourceManager.GetSources()); + + foreach (var error in _backgroundPackageUpdateStatus.Value.Errors) { + for (var scan = error; scan != null; scan = scan.InnerException) { + Services.Notifier.Warning(T("Package retrieve error: {0}", scan.Message)); + } + } + + return View(view, new PackageList { + Entries = _backgroundPackageUpdateStatus.Value.Entries + .Where(updatePackageEntry => updatePackageEntry.ExtensionsDescriptor.ExtensionType.Equals(extensionType)) + }); + } + + public ActionResult RefreshThemes() { + _packageUpdateService.TriggerRefresh(); + _backgroundPackageUpdateStatus.Value = null; + return RedirectToAction("ThemesUpdates"); + } + + public ActionResult RefreshModules() { + _packageUpdateService.TriggerRefresh(); + _backgroundPackageUpdateStatus.Value = null; + return RedirectToAction("ModulesUpdates"); + } + + 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 }); + } + + public ActionResult Uninstall(string packageId, string returnUrl) { + if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to install packages"))) + return new HttpUnauthorizedResult(); + + try { + _packageUpdateService.Uninstall(packageId); + } + catch (Exception exception) { + Logger.Error(exception, "Error un-installing package {0}", packageId); + _notifier.Error(T("Error un-installing package.")); + for (Exception scan = exception; scan != null; scan = scan.InnerException) { + _notifier.Error(T("{0}", scan.Message)); + } + } + + int reportId = CreateReport(T("Package Uninstall"), T("Un-installation of package {0}", packageId)); + + 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.PackageManager/Module.txt b/src/Orchard.Web/Modules/Orchard.PackageManager/Module.txt new file mode 100644 index 000000000..2d4c66e5b --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.PackageManager/Module.txt @@ -0,0 +1,13 @@ +Name: Orchard.PackageManager +AntiForgery: enabled +Author: The Orchard Team +Website: http://orchardproject.net +Version: 1.0.20 +OrchardVersion: 1.0.20 +Description: Description for the module +Features: + Orchard.PackageManager: + Name: Package Manager + Description: Allow package updates. + Category: Packaging + Dependencies: Gallery \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.PackageManager/Orchard.PackageManager.csproj b/src/Orchard.Web/Modules/Orchard.PackageManager/Orchard.PackageManager.csproj new file mode 100644 index 000000000..979ece468 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.PackageManager/Orchard.PackageManager.csproj @@ -0,0 +1,133 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {E66935C0-830B-466E-A571-4AEED7057CBD} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Orchard.PackageManager + Orchard.PackageManager + v4.0 + false + + + 3.5 + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + + + pdbonly + true + bin\ + TRACE + prompt + 4 + AllRules.ruleset + + + + + + + 3.5 + + + + False + ..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll + + + + + + + + + + + + + + + + + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} + Orchard.Framework + + + {9916839C-39FC-4CEB-A5AF-89CA7E87119F} + Orchard.Core + + + {DFD137A2-DDB5-4D22-BE0D-FA9AD4C8B059} + Orchard.Packaging + + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)\..\Manifests + + + + + + + + + + + + False + True + 45979 + / + + + False + True + http://orchard.codeplex.com + False + + + + + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.PackageManager/Properties/AssemblyInfo.cs b/src/Orchard.Web/Modules/Orchard.PackageManager/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..e9e5a5563 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.PackageManager/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Orchard.PackageManager")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("Orchard")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5c928dc2-aa81-4452-99b4-e4c76600c84b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Orchard.Web/Modules/Orchard.PackageManager/Services/BackgroundPackageUpdateStatus.cs b/src/Orchard.Web/Modules/Orchard.PackageManager/Services/BackgroundPackageUpdateStatus.cs new file mode 100644 index 000000000..6cfe03b5d --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.PackageManager/Services/BackgroundPackageUpdateStatus.cs @@ -0,0 +1,9 @@ +namespace Orchard.PackageManager.Services { + public interface IBackgroundPackageUpdateStatus : ISingletonDependency { + PackagesStatusResult Value { get; set; } + } + + public class BackgroundPackageUpdateStatus : IBackgroundPackageUpdateStatus { + public PackagesStatusResult Value { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.PackageManager/Services/BackgroundPackageUpdateTask.cs b/src/Orchard.Web/Modules/Orchard.PackageManager/Services/BackgroundPackageUpdateTask.cs new file mode 100644 index 000000000..f242cc600 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.PackageManager/Services/BackgroundPackageUpdateTask.cs @@ -0,0 +1,32 @@ +using Orchard.Packaging.Services; +using Orchard.Tasks; + +namespace Orchard.PackageManager.Services { + /// + /// Background task responsible for fetching feeds from the Gallery into the + /// BackgroundPackageUpdateStatus singleton dependency. + /// The purpose is to make sure we don't block the Admin panel the first time + /// it's accessed when the PackageManager feature is enabled. The first time + /// the panel is accessed, the list of updates will be empty. It will be non empty + /// only if the user asks for an explicit refresh or after the first background + /// task sweep. + /// + public class BackgroundPackageUpdateTask : IBackgroundTask { + private readonly IPackageUpdateService _packageUpdateService; + private readonly IPackagingSourceManager _packagingSourceManager; + private readonly IBackgroundPackageUpdateStatus _backgroundPackageUpdateStatus; + + public BackgroundPackageUpdateTask(IPackageUpdateService packageUpdateService, + IPackagingSourceManager packagingSourceManager, + IBackgroundPackageUpdateStatus backgroundPackageUpdateStatus) { + + _packageUpdateService = packageUpdateService; + _packagingSourceManager = packagingSourceManager; + _backgroundPackageUpdateStatus = backgroundPackageUpdateStatus; + } + + public void Sweep() { + _backgroundPackageUpdateStatus.Value = _packageUpdateService.GetPackagesStatus(_packagingSourceManager.GetSources()); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.PackageManager/Services/FolderUpdater.cs b/src/Orchard.Web/Modules/Orchard.PackageManager/Services/FolderUpdater.cs new file mode 100644 index 000000000..122a8f62f --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.PackageManager/Services/FolderUpdater.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Orchard.Localization; +using Orchard.Logging; +using Orchard.UI.Notify; + +namespace Orchard.PackageManager.Services { + public interface IFolderUpdater : IDependency { + void Backup(DirectoryInfo existingFolder, DirectoryInfo backupfolder); + void Update(DirectoryInfo destinationFolder, DirectoryInfo newFolder); + } + + 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; + T = NullLocalizer.Instance; + Logger = NullLogger.Instance; + } + + public Localizer T { get; set; } + public ILogger Logger { get; set; } + + public void Backup(DirectoryInfo existingFolder, DirectoryInfo backupfolder) { + 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); + } + } + + private void CopyFile(DirectoryInfo sourceFolder, string fileName, DirectoryInfo destinationFolder) { + var sourceFile = new FileInfo(Path.Combine(sourceFolder.FullName, fileName)); + var destFile = new FileInfo(Path.Combine(destinationFolder.FullName, fileName)); + + // If destination file exist, overwrite only if changed + if (destFile.Exists) { + if (sourceFile.Length == destFile.Length) { + var source = File.ReadAllBytes(sourceFile.FullName); + var dest = File.ReadAllBytes(destFile.FullName); + if (source.SequenceEqual(dest)) { + //_notifier.Information(T("Skipping file \"{0}\" because it is the same content as the source file", destFile.FullName)); + return; + } + } + } + + // Create destination directory + if (!destFile.Directory.Exists) { + destFile.Directory.Create(); + } + + File.Copy(sourceFile.FullName, destFile.FullName, true); + } + + private FolderContent GetFolderContent(DirectoryInfo folder) { + var files = new List(); + GetFolderContent(folder, "", files); + return new FolderContent { Folder = folder, Files = files }; + } + + private void GetFolderContent(DirectoryInfo folder, string prefix, List files) { + if (!folder.Exists) + return; + + foreach (var file in folder.GetFiles()) { + files.Add(Path.Combine(prefix, file.Name)); + } + + foreach (var child in folder.GetDirectories()) { + GetFolderContent(child, Path.Combine(prefix, child.Name), files); + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.PackageManager/Services/PackageUpdateManager.cs b/src/Orchard.Web/Modules/Orchard.PackageManager/Services/PackageUpdateManager.cs new file mode 100644 index 000000000..21c7a5a09 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.PackageManager/Services/PackageUpdateManager.cs @@ -0,0 +1,285 @@ +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.Packaging.Services; +using Orchard.Services; +using Orchard.UI.Notify; + +namespace Orchard.PackageManager.Services { + public class PackagesStatusResult { + public IEnumerable Entries { get; set; } + public IEnumerable Errors { get; set; } + } + + public class UpdatePackageEntry { + public ExtensionDescriptor ExtensionsDescriptor { get; set; } + public IList PackageVersions { get; set; } + + /// + /// Return version to install if out-of-date, null otherwise. + /// + public PackagingEntry NewVersionToInstall { + get { + PackagingEntry updateToVersion = null; + var latestUpdate = this.PackageVersions.OrderBy(v => new Version(v.Version)).Last(); + if (new Version(latestUpdate.Version) > new Version(this.ExtensionsDescriptor.Version)) { + updateToVersion = latestUpdate; + } + return updateToVersion; + } + } + } + + public interface IPackageUpdateService : IDependency { + PackagesStatusResult GetPackagesStatus(IEnumerable sources); + void TriggerRefresh(); + void Update(PackagingEntry entry); + void Uninstall(string packageId); + } + + public class PackageUpdateService : IPackageUpdateService { + private readonly IPackagingSourceManager _packagingSourceManager; + private readonly IExtensionManager _extensionManager; + 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) { + + _packagingSourceManager = packagingSourceManager; + _extensionManager = extensionManager; + _cacheManager = cacheManager; + _clock = clock; + _signals = signals; + _notifier = notifier; + _virtualPathProvider = virtualPathProvider; + _packageManager = packageManager; + _folderUpdater = folderUpdater; + T = NullLocalizer.Instance; + Logger = NullLogger.Instance; + } + + public Localizer T { get; set; } + public ILogger Logger { get; set; } + + public PackagesStatusResult GetPackagesStatus(IEnumerable sources) { + var result = new PackagesStatusResult { + Entries = new List(), + Errors = new List() + }; + + foreach (var source in sources) { + var sourceResult = GetPackages(source); + result.Entries = result.Entries.Concat(sourceResult.Entries); + result.Errors = result.Errors.Concat(sourceResult.Errors); + } + + return result; + } + + public void TriggerRefresh() { + _signals.Trigger("PackageUpdateService"); + } + + private PackagesStatusResult GetPackages(PackagingSource packagingSource) { + return _cacheManager.Get(packagingSource.FeedUrl, ctx => { + // Refresh every minute or when signal was triggered + ctx.Monitor(_clock.When(TimeSpan.FromMinutes(5))); + ctx.Monitor(_signals.When("PackageUpdateService")); + + // We cache exception because we are calling on a network feed, and failure may + // take quite some time. + var result = new PackagesStatusResult { + Entries = new List(), + Errors = new List() + }; + try { + result.Entries = GetPackagesWorker(packagingSource); + } + catch (Exception e) { + result.Errors = new[] { e }; + } + return result; + }); + } + + private IEnumerable GetPackagesWorker(PackagingSource packagingSource) { + var list = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var extensions = _extensionManager.AvailableExtensions(); + foreach (var extension in extensions) { + var packageId = PackageBuilder.BuildPackageId(extension.Id, extension.ExtensionType); + + GetOrAddEntry(list, packageId).ExtensionsDescriptor = extension; + } + + var packages = _packagingSourceManager.GetExtensionList(packagingSource) + .ToList() + .GroupBy(p => p.PackageId, StringComparer.OrdinalIgnoreCase); + + foreach (var package in packages) { + var entry = GetOrAddEntry(list, package.Key); + entry.PackageVersions = entry.PackageVersions.Concat(package).ToList(); + } + + return list.Values.Where(e => e.ExtensionsDescriptor != null && e.PackageVersions.Any()); + } + + private UpdatePackageEntry GetOrAddEntry(Dictionary list, string packageId) { + UpdatePackageEntry entry; + if (!list.TryGetValue(packageId, out entry)) { + entry = new UpdatePackageEntry { PackageVersions = new List() }; + list.Add(packageId, entry); + } + 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.PackageManager/ViewModels/PackageList.cs b/src/Orchard.Web/Modules/Orchard.PackageManager/ViewModels/PackageList.cs new file mode 100644 index 000000000..dbe9f6e57 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.PackageManager/ViewModels/PackageList.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; +using Orchard.PackageManager.Services; + +namespace Orchard.PackageManager.ViewModels { + public class PackageList { + public IEnumerable Entries { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.PackageManager/Views/Admin/ModulesUpdate.cshtml b/src/Orchard.Web/Modules/Orchard.PackageManager/Views/Admin/ModulesUpdate.cshtml new file mode 100644 index 000000000..f63e5f6e8 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.PackageManager/Views/Admin/ModulesUpdate.cshtml @@ -0,0 +1,76 @@ +@using Orchard.Modules.Extensions +@using Orchard.Mvc.Html; +@using Orchard.PackageManager.ViewModels; +@using Orchard.Packaging.Services; +@using Orchard.Packaging.Models; +@using Orchard.Environment.Extensions.Models; +@using Orchard.Utility.Extensions; +@model PackageList + +@{ Layout.Title = T("Modules").ToString(); } + +@functions { + public string InstallAction(PackagingEntry package) { + return Url.Action("Install", "Admin", new { + area = "Orchard.PackageManager", + packageId = package.PackageId, + version = package.Version, + sourceId = package.Source.Id, + returnUrl = ViewContext.RequestContext.HttpContext.Request.ToUrlString() + }); + } + public string UninstallAction(PackagingEntry package) { + return Url.Action("Uninstall", "Admin", new { + area = "Orchard.PackageManager", + packageId = package.PackageId, + returnUrl = ViewContext.RequestContext.HttpContext.Request.ToUrlString() + }); + } +} + +@if (Model.Entries.Count() <= 0) { +

No package updates available.

+} else { +
    + @foreach (var module in Model.Entries) { +
  • +
    +
    +

    @module.ExtensionsDescriptor.Name - @module.ExtensionsDescriptor.Version + @if (module.NewVersionToInstall != null) { +  |  + @Html.Link(T("Install Latest").Text, InstallAction(module.NewVersionToInstall)) + + } else { +  |  + Up-to-date + + } +  |  + @Html.Link(T("Uninstall").Text, UninstallAction(module.PackageVersions.First())) + +

    +
      + @foreach (var version in module.PackageVersions.OrderByDescending(e => new Version(e.Version))) { +
    • + @T("Gallery Version {0} - Last Updated UTC: {1}", version.Version, version.LastUpdated.ToUniversalTime()) + @if (!string.IsNullOrWhiteSpace(version.GalleryDetailsUrl)) { +  (@T("details")) + } + +  |  + @if (new Version(version.Version) == new Version(module.ExtensionsDescriptor.Version)) { + @Html.Link(T("Install again").Text, InstallAction(version)) + } else if (new Version(version.Version) > new Version(module.ExtensionsDescriptor.Version)) { + @Html.Link(T("Upgrade").Text, InstallAction(version)) + } else { + @Html.Link(T("Downgrade (not recommended)").Text, InstallAction(version)) + } +
    • + } +
    +
    +
    +
  • } +
+} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.PackageManager/Views/Admin/ThemesUpdate.cshtml b/src/Orchard.Web/Modules/Orchard.PackageManager/Views/Admin/ThemesUpdate.cshtml new file mode 100644 index 000000000..62bfc471e --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.PackageManager/Views/Admin/ThemesUpdate.cshtml @@ -0,0 +1,76 @@ +@using Orchard.Modules.Extensions +@using Orchard.Mvc.Html; +@using Orchard.PackageManager.ViewModels; +@using Orchard.Packaging.Services; +@using Orchard.Packaging.Models; +@using Orchard.Environment.Extensions.Models; +@using Orchard.Utility.Extensions; +@model PackageList + +@{ Layout.Title = T("Themes").ToString(); } + +@functions { + public string InstallAction(PackagingEntry package) { + return Url.Action("Install", "Admin", new { + area = "Orchard.PackageManager", + packageId = package.PackageId, + version = package.Version, + sourceId = package.Source.Id, + returnUrl = ViewContext.RequestContext.HttpContext.Request.ToUrlString() + }); + } + public string UninstallAction(PackagingEntry package) { + return Url.Action("Uninstall", "Admin", new { + area = "Orchard.PackageManager", + packageId = package.PackageId, + returnUrl = ViewContext.RequestContext.HttpContext.Request.ToUrlString() + }); + } +} + +@if (Model.Entries.Count() <= 0) { +

No package updates available.

+} else { +
    + @foreach (var module in Model.Entries) { +
  • +
    +
    +

    @module.ExtensionsDescriptor.Name - @module.ExtensionsDescriptor.Version + @if (module.NewVersionToInstall != null) { +  |  + @Html.Link(T("Install Latest").Text, InstallAction(module.NewVersionToInstall)) + + } else { +  |  + Up-to-date + + } +  |  + @Html.Link(T("Uninstall").Text, UninstallAction(module.PackageVersions.First())) + +

    +
      + @foreach (var version in module.PackageVersions.OrderByDescending(e => new Version(e.Version))) { +
    • + @T("Gallery Version {0} - Last Updated UTC: {1}", version.Version, version.LastUpdated.ToUniversalTime()) + @if (!string.IsNullOrWhiteSpace(version.GalleryDetailsUrl)) { +  (@T("details")) + } + +  |  + @if (new Version(version.Version) == new Version(module.ExtensionsDescriptor.Version)) { + @Html.Link(T("Install again").Text, InstallAction(version)) + } else if (new Version(version.Version) > new Version(module.ExtensionsDescriptor.Version)) { + @Html.Link(T("Upgrade").Text, InstallAction(version)) + } else { + @Html.Link(T("Downgrade (not recommended)").Text, InstallAction(version)) + } +
    • + } +
    +
    +
    +
  • } +
+} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.PackageManager/Views/Web.config b/src/Orchard.Web/Modules/Orchard.PackageManager/Views/Web.config new file mode 100644 index 000000000..b7d215131 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.PackageManager/Views/Web.config @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.PackageManager/Web.config b/src/Orchard.Web/Modules/Orchard.PackageManager/Web.config new file mode 100644 index 000000000..5884c5879 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.PackageManager/Web.config @@ -0,0 +1,39 @@ + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Packaging/AdminMenu.cs index 08ef965be..a90029b18 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/AdminMenu.cs @@ -7,7 +7,6 @@ namespace Orchard.Packaging { [OrchardFeature("Gallery")] public class AdminMenu : INavigationProvider { public Localizer T { get; set; } - public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/GalleryController.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/GalleryController.cs index c2ec19646..37f13cd67 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/GalleryController.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Controllers/GalleryController.cs @@ -10,7 +10,6 @@ using Orchard.Environment.Extensions; using Orchard.Environment.Extensions.Models; using Orchard.Localization; using Orchard.Logging; -using Orchard.Packaging.GalleryServer; using Orchard.Packaging.Models; using Orchard.Packaging.Services; using Orchard.Packaging.ViewModels; diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/NugetLogger.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Models/NugetLogger.cs similarity index 92% rename from src/Orchard.Web/Modules/Orchard.Packaging/Services/NugetLogger.cs rename to src/Orchard.Web/Modules/Orchard.Packaging/Models/NugetLogger.cs index 551b76c8c..e9e735510 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/NugetLogger.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Models/NugetLogger.cs @@ -3,7 +3,7 @@ using NuGet; using Orchard.Localization; using Orchard.UI.Notify; -namespace Orchard.Packaging.Services { +namespace Orchard.Packaging.Models { public class NugetLogger : ILogger { private readonly INotifier _notifier; diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageData.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Models/PackageData.cs similarity index 84% rename from src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageData.cs rename to src/Orchard.Web/Modules/Orchard.Packaging/Models/PackageData.cs index ff7091c06..14d0fcba9 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageData.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Models/PackageData.cs @@ -1,6 +1,6 @@ using System.IO; -namespace Orchard.Packaging.Services { +namespace Orchard.Packaging.Models { public class PackageData { public string ExtensionType { get; set; } public string ExtensionName { get; set; } diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingEntry.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Models/PackagingEntry.cs similarity index 89% rename from src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingEntry.cs rename to src/Orchard.Web/Modules/Orchard.Packaging/Models/PackagingEntry.cs index aa5258fe4..0b86a30ec 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingEntry.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Models/PackagingEntry.cs @@ -1,7 +1,6 @@ using System; -using Orchard.Packaging.Models; -namespace Orchard.Packaging.Services { +namespace Orchard.Packaging.Models { public class PackagingEntry { public PackagingSource Source { get; set; } public string Title { get; set; } diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Orchard.Packaging.csproj b/src/Orchard.Web/Modules/Orchard.Packaging/Orchard.Packaging.csproj index 8cdee0e16..f60e69348 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Orchard.Packaging.csproj +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Orchard.Packaging.csproj @@ -78,12 +78,12 @@ - + + - - + diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/IPackageManager.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Services/IPackageManager.cs index c39e37bef..60d5d73a2 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/IPackageManager.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Services/IPackageManager.cs @@ -1,4 +1,5 @@ using NuGet; +using Orchard.Packaging.Models; namespace Orchard.Packaging.Services { public interface IPackageManager : IDependency { diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/IPackagingSourceManager.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Services/IPackagingSourceManager.cs index 4df2d2653..6ad7baf67 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/IPackagingSourceManager.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Services/IPackagingSourceManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using Orchard.Packaging.GalleryServer; using Orchard.Packaging.Models; diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageInstaller.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageInstaller.cs index 135a8bc11..42c073892 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageInstaller.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageInstaller.cs @@ -5,6 +5,7 @@ using NuGet; using Orchard.Environment.Extensions; using Orchard.Environment.Extensions.Models; using Orchard.Localization; +using Orchard.Packaging.Models; using Orchard.UI.Notify; using NuGetPackageManager = NuGet.PackageManager; diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageManager.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageManager.cs index 6c01318bb..81c15ca86 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageManager.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackageManager.cs @@ -4,6 +4,7 @@ using NuGet; using Orchard.Environment.Extensions; using Orchard.Environment.Extensions.Models; using Orchard.Localization; +using Orchard.Packaging.Models; namespace Orchard.Packaging.Services { [OrchardFeature("PackagingServices")] diff --git a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingSourceManager.cs b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingSourceManager.cs index bd8218c5b..1c99560ab 100644 --- a/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingSourceManager.cs +++ b/src/Orchard.Web/Modules/Orchard.Packaging/Services/PackagingSourceManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using Orchard.Data; using Orchard.Environment.Extensions; using Orchard.Localization; diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs index 0d90c436b..6fdc55647 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.Themes/Controllers/AdminController.cs @@ -80,7 +80,7 @@ namespace Orchard.Themes.Controllers { }) .Select(extensionDescriptor => new ThemeEntry(extensionDescriptor) { NeedsUpdate = featuresThatNeedUpdate.Contains(extensionDescriptor.Id), - IsRecentlyInstalled = _themeService.UpdateIsRecentlyInstalled(extensionDescriptor), + IsRecentlyInstalled = _themeService.IsRecentlyInstalled(extensionDescriptor), Enabled = _shellDescriptor.Features.Any(sf => sf.Name == extensionDescriptor.Id) }) .ToArray(); diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Services/IThemeService.cs b/src/Orchard.Web/Modules/Orchard.Themes/Services/IThemeService.cs index 8ade7cee1..f9fa25535 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Services/IThemeService.cs +++ b/src/Orchard.Web/Modules/Orchard.Themes/Services/IThemeService.cs @@ -4,6 +4,6 @@ namespace Orchard.Themes.Services { public interface IThemeService : IDependency { void DisableThemeFeatures(string themeName); void EnableThemeFeatures(string themeName); - bool UpdateIsRecentlyInstalled(ExtensionDescriptor module); + bool IsRecentlyInstalled(ExtensionDescriptor module); } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Services/ThemeService.cs b/src/Orchard.Web/Modules/Orchard.Themes/Services/ThemeService.cs index fb33c0c20..a5b8452f3 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Services/ThemeService.cs +++ b/src/Orchard.Web/Modules/Orchard.Themes/Services/ThemeService.cs @@ -114,10 +114,10 @@ namespace Orchard.Themes.Services { } /// - /// Updates the recently installed flag by using the project's last written time. + /// Determines if a theme was recently installed by using the project's last written time. /// /// The extension descriptor. - public bool UpdateIsRecentlyInstalled(ExtensionDescriptor descriptor) { + public bool IsRecentlyInstalled(ExtensionDescriptor descriptor) { string projectFile = GetManifestPath(descriptor); if (!string.IsNullOrEmpty(projectFile)) { // If project file was modified less than 24 hours ago, the module was recently deployed diff --git a/src/Orchard.sln b/src/Orchard.sln index ec292d72c..8debf17a7 100644 --- a/src/Orchard.sln +++ b/src/Orchard.sln @@ -108,6 +108,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.DesignerTools", "Or EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.MediaPicker", "Orchard.Web\Modules\Orchard.MediaPicker\Orchard.MediaPicker.csproj", "{43D0EC0B-1955-4566-8D31-7B9102DA1703}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.PackageManager", "Orchard.Web\Modules\Orchard.PackageManager\Orchard.PackageManager.csproj", "{E66935C0-830B-466E-A571-4AEED7057CBD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution CodeCoverage|Any CPU = CodeCoverage|Any CPU @@ -571,6 +573,16 @@ Global {43D0EC0B-1955-4566-8D31-7B9102DA1703}.FxCop|Any CPU.Build.0 = Release|Any CPU {43D0EC0B-1955-4566-8D31-7B9102DA1703}.Release|Any CPU.ActiveCfg = Release|Any CPU {43D0EC0B-1955-4566-8D31-7B9102DA1703}.Release|Any CPU.Build.0 = Release|Any CPU + {E66935C0-830B-466E-A571-4AEED7057CBD}.CodeCoverage|Any CPU.ActiveCfg = Release|Any CPU + {E66935C0-830B-466E-A571-4AEED7057CBD}.CodeCoverage|Any CPU.Build.0 = Release|Any CPU + {E66935C0-830B-466E-A571-4AEED7057CBD}.Coverage|Any CPU.ActiveCfg = Release|Any CPU + {E66935C0-830B-466E-A571-4AEED7057CBD}.Coverage|Any CPU.Build.0 = Release|Any CPU + {E66935C0-830B-466E-A571-4AEED7057CBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E66935C0-830B-466E-A571-4AEED7057CBD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E66935C0-830B-466E-A571-4AEED7057CBD}.FxCop|Any CPU.ActiveCfg = Release|Any CPU + {E66935C0-830B-466E-A571-4AEED7057CBD}.FxCop|Any CPU.Build.0 = Release|Any CPU + {E66935C0-830B-466E-A571-4AEED7057CBD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E66935C0-830B-466E-A571-4AEED7057CBD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -608,6 +620,7 @@ Global {99002B65-86F7-415E-BF4A-381AA8AB9CCC} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {4A4595EF-6C37-4F99-96ED-4AE0B9E438D3} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {43D0EC0B-1955-4566-8D31-7B9102DA1703} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} + {E66935C0-830B-466E-A571-4AEED7057CBD} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {ABC826D4-2FA1-4F2F-87DE-E6095F653810} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {F112851D-B023-4746-B6B1-8D2E5AD8F7AA} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {6CB3EB30-F725-45C0-9742-42599BA8E8D2} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} diff --git a/src/Orchard/Reports/Services/IReportsCoordinator.cs b/src/Orchard/Reports/Services/IReportsCoordinator.cs index 6df87f284..d8104b89b 100644 --- a/src/Orchard/Reports/Services/IReportsCoordinator.cs +++ b/src/Orchard/Reports/Services/IReportsCoordinator.cs @@ -1,6 +1,6 @@ namespace Orchard.Reports.Services { public interface IReportsCoordinator : IDependency { void Add(string reportKey, ReportEntryType type, string message); - void Register(string reportKey, string activityName, string title); + int Register(string reportKey, string activityName, string title); } } diff --git a/src/Orchard/Reports/Services/ReportsCoordinator.cs b/src/Orchard/Reports/Services/ReportsCoordinator.cs index 042dfa278..a399b3bd9 100644 --- a/src/Orchard/Reports/Services/ReportsCoordinator.cs +++ b/src/Orchard/Reports/Services/ReportsCoordinator.cs @@ -27,8 +27,10 @@ namespace Orchard.Reports.Services { _reportsManager.Add(_reports[reportKey], type, message); } - public void Register(string reportKey, string activityName, string title) { - _reports.Add(reportKey, _reportsManager.CreateReport(title, activityName)); + public int Register(string reportKey, string activityName, string title) { + int reportId = _reportsManager.CreateReport(title, activityName); + _reports.Add(reportKey, reportId); + return reportId; } } }