Aligning package update page to gallery. Moving the update feature to the packaging module as a feature.

--HG--
branch : dev
This commit is contained in:
Andre Rodrigues
2011-02-22 10:01:21 -08:00
parent d0f9829a42
commit e7e60d2435
24 changed files with 347 additions and 565 deletions

View File

@@ -1,5 +1,8 @@
using Orchard.Environment.Extensions;
using System.Linq;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.Localization;
using Orchard.Packaging.Services;
using Orchard.UI.Navigation;
using Orchard.Security;
@@ -9,6 +12,14 @@ namespace Orchard.Packaging {
public Localizer T { get; set; }
public string MenuName { get { return "admin"; } }
private readonly IBackgroundPackageUpdateStatus _backgroundPackageUpdateStatus;
public AdminMenu(IBackgroundPackageUpdateStatus backgroundPackageUpdateStatus) {
_backgroundPackageUpdateStatus = backgroundPackageUpdateStatus;
}
public AdminMenu() {}
public void GetNavigation(NavigationBuilder builder) {
builder.Add(T("Themes"), "25", menu => menu
.Add(T("Available"), "1", item => item.Action("Themes", "Gallery", new { area = "Orchard.Packaging" })
@@ -21,6 +32,37 @@ namespace Orchard.Packaging {
builder.Add(T("Configuration"), "50", menu => menu
.Add(T("Feeds"), "25", item => item.Action("Sources", "Gallery", new { area = "Orchard.Packaging" })
.Permission(StandardPermissions.SiteOwner)));
if (_backgroundPackageUpdateStatus != null) {
// Only available if feature is enabled
int modulesUpdateCount = GetUpdateCount(DefaultExtensionTypes.Module);
LocalizedString modulesCaption = (modulesUpdateCount == 0 ? T("Updates") : T("Updates ({0})", modulesUpdateCount));
int themesUpdateCount = GetUpdateCount(DefaultExtensionTypes.Theme);
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", "GalleryUpdates", new { area = "Orchard.Packaging" })
.Permission(StandardPermissions.SiteOwner).LocalNav()));
builder.Add(T("Themes"), "25", menu => menu
.Add(themesCaption, "30.0", item => item.Action("ThemesUpdates", "GalleryUpdates", new { area = "Orchard.Packaging" })
.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;
}
}
}
}

View File

@@ -0,0 +1,196 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Orchard.DisplayManagement;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Packaging.Models;
using Orchard.Packaging.Services;
using Orchard.Packaging.ViewModels;
using Orchard.Reports;
using Orchard.Reports.Services;
using Orchard.Security;
using Orchard.Themes;
using Orchard.UI.Admin;
using Orchard.UI.Navigation;
using Orchard.UI.Notify;
namespace Orchard.Packaging.Controllers {
[OrchardFeature("Gallery.Updates")]
[Themed, Admin]
public class GalleryUpdatesController : 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 GalleryUpdatesController(IOrchardServices services,
IPackagingSourceManager packagingSourceManager,
INotifier notifier,
IPackageUpdateService packageUpdateService,
IBackgroundPackageUpdateStatus backgroundPackageUpdateStatus,
IReportsCoordinator reportsCoordinator,
IReportsManager reportsManager,
IShapeFactory shapeFactory) {
_packagingSourceManager = packagingSourceManager;
_notifier = notifier;
_packageUpdateService = packageUpdateService;
_backgroundPackageUpdateStatus = backgroundPackageUpdateStatus;
_reportsCoordinator = reportsCoordinator;
_reportsManager = reportsManager;
Services = services;
Shape = shapeFactory;
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
}
public IOrchardServices Services { get; private set; }
public Localizer T { get; set; }
public ILogger Logger { get; set; }
public dynamic Shape { get; set; }
public ActionResult ThemesUpdates(int? reportId, PagerParameters pagerParameters) {
return PackageUpdate("ThemesUpdates", DefaultExtensionTypes.Theme, reportId, pagerParameters);
}
public ActionResult ModulesUpdates(int? reportId, PagerParameters pagerParameters) {
return PackageUpdate("ModulesUpdates", DefaultExtensionTypes.Module, reportId, pagerParameters);
}
private ActionResult PackageUpdate(string view, string extensionType, int? reportId, PagerParameters pagerParameters) {
if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to install packages")))
return new HttpUnauthorizedResult();
Pager pager = new Pager(Services.WorkContext.CurrentSite, pagerParameters);
if (reportId != null)
CreateNotificationsFromReport(reportId.Value);
if (!_packagingSourceManager.GetSources().Any()) {
Services.Notifier.Error(T("No Gallery feed configured"));
return View(view, new PackagingListViewModel { Entries = new List<UpdatePackageEntry>() });
}
// 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));
}
}
IEnumerable<UpdatePackageEntry> updatedPackages = _backgroundPackageUpdateStatus.Value.Entries
.Where(updatePackageEntry =>
updatePackageEntry.ExtensionsDescriptor.ExtensionType.Equals(extensionType) &&
updatePackageEntry.NewVersionToInstall != null);
int totalItemCount = updatedPackages.Count();
if (pager.PageSize != 0) {
updatedPackages = updatedPackages.Skip((pager.Page - 1) * pager.PageSize).Take(pager.PageSize);
}
return View(view, new PackagingListViewModel {
Entries = updatedPackages,
Pager = Shape.Pager(pager).TotalItemCount(totalItemCount)
});
}
public ActionResult Install(string packageId, string version, int sourceId, string returnUrl) {
if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to install packages")))
return new HttpUnauthorizedResult();
_backgroundPackageUpdateStatus.Value =
_backgroundPackageUpdateStatus.Value ??
_packageUpdateService.GetPackagesStatus(_packagingSourceManager.GetSources());
var entry = _backgroundPackageUpdateStatus.Value
.Entries
.SelectMany(e => e.PackageVersions)
.Where(e => e.PackageId == packageId && e.Version == version && e.Source.Id == sourceId)
.FirstOrDefault();
if (entry == null) {
return HttpNotFound();
}
try {
_packageUpdateService.Update(entry);
}
catch (Exception exception) {
Logger.Error(exception, "Error installing package {0}, version {1} from source {2}", packageId, version, sourceId);
_notifier.Error(T("Error installing package update."));
for (Exception scan = exception; scan != null; scan = scan.InnerException) {
_notifier.Error(T("{0}", scan.Message));
}
}
int reportId = CreateReport(T("Package Update"), T("Update of package {0} to version {1}", packageId, version));
return RedirectToAction(returnUrl, new { reportId });
}
private void CreateNotificationsFromReport(int reportId) {
// If we have notification in TempData, we don't need to display the
// report as notifications (i.e. the AppDomain hasn't been restarted)
// Note: This relies on an implementation detail of "Orchard.UI.Notify.NotifyFilter"
if (TempData["messages"] != null)
return;
var report = _reportsManager.Get(reportId);
if (report == null)
return;
if (report.Entries.Any()) {
_notifier.Information(T("Application has been restarted. The following notifications originate from report #{0}:", reportId));
}
foreach(var entry in report.Entries) {
switch(entry.Type) {
case ReportEntryType.Information:
_notifier.Add(NotifyType.Information, T(entry.Message));
break;
case ReportEntryType.Warning:
_notifier.Add(NotifyType.Warning, T(entry.Message));
break;
case ReportEntryType.Error:
default:
_notifier.Add(NotifyType.Error, T(entry.Message));
break;
}
}
}
private int CreateReport(LocalizedString activityName, LocalizedString title) {
// Create a persistent report with all notifications, in case the application needs to be restarted
const string reportKey = "PackageManager";
int reportId = _reportsCoordinator.Register(reportKey, activityName.Text, title.Text);
foreach(var notifyEntry in _notifier.List()) {
switch (notifyEntry.Type) {
case NotifyType.Information:
_reportsCoordinator.Add(reportKey, ReportEntryType.Information, notifyEntry.Message.Text);
break;
case NotifyType.Warning:
_reportsCoordinator.Add(reportKey, ReportEntryType.Warning, notifyEntry.Message.Text);
break;
case NotifyType.Error:
default:
_reportsCoordinator.Add(reportKey, ReportEntryType.Error, notifyEntry.Message.Text);
break;
}
}
return reportId;
}
}
}

View File

@@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.Linq;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.Localization;
using Orchard.PackageManager.Events;
using Orchard.Packaging.Models;
using Orchard.Packaging.Services;
namespace Orchard.Packaging.Events {
[OrchardFeature("Gallery.Updates")]
public class ExtensionDisplayEventHandler : IExtensionDisplayEventHandler {
private readonly IBackgroundPackageUpdateStatus _backgroundPackageUpdateStatus;
private readonly IPackagingSourceManager _packagingSourceManager;
private readonly IPackageUpdateService _packageUpdateService;
public ExtensionDisplayEventHandler(IBackgroundPackageUpdateStatus backgroundPackageUpdateStatus,
IPackagingSourceManager packagingSourceManager,
IPackageUpdateService packageUpdateService) {
_backgroundPackageUpdateStatus = backgroundPackageUpdateStatus;
_packagingSourceManager = packagingSourceManager;
_packageUpdateService = packageUpdateService;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public IEnumerable<string> Displaying(ExtensionDescriptor extensionDescriptor) {
// Get status from background task state or directly
_backgroundPackageUpdateStatus.Value =
_backgroundPackageUpdateStatus.Value ??
_packageUpdateService.GetPackagesStatus(_packagingSourceManager.GetSources());
UpdatePackageEntry updatePackageEntry = _backgroundPackageUpdateStatus.Value.Entries
.Where(package => package.ExtensionsDescriptor.Id.Equals(extensionDescriptor.Id)).FirstOrDefault();
if (updatePackageEntry != null) {
if (updatePackageEntry.NewVersionToInstall != null) {
yield return T("New version available: {0}", updatePackageEntry.NewVersionToInstall.Version).ToString();
}
}
}
}
}

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
using Orchard.Environment.Extensions.Models;
using Orchard.Events;
namespace Orchard.PackageManager.Events {
public interface IExtensionDisplayEventHandler : IEventHandler {
/// <summary>
/// Called before an extension is displayed
/// </summary>
IEnumerable<string> Displaying(ExtensionDescriptor extensionDescriptor);
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.Environment.Extensions.Models;
namespace Orchard.Packaging.Models {
public class PackagesStatusResult {
public IEnumerable<UpdatePackageEntry> Entries { get; set; }
public IEnumerable<Exception> Errors { get; set; }
}
public class UpdatePackageEntry {
public ExtensionDescriptor ExtensionsDescriptor { get; set; }
public IList<PackagingEntry> PackageVersions { get; set; }
/// <summary>
/// Return version to install if out-of-date, null otherwise.
/// </summary>
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;
}
}
}
}

View File

@@ -20,3 +20,8 @@ Features:
Description: Module gallery management.
Category: Packaging
Dependencies: Orchard.Packaging
Gallery.Updates
Name: Gallery Updates
Description: Manages updates for packages.
Category: Packaging
Dependencies: Gallery

View File

@@ -60,11 +60,15 @@
<ItemGroup>
<Compile Include="AdminMenu.cs" />
<Compile Include="Commands\PackagingCommands.cs" />
<Compile Include="Controllers\GalleryUpdatesController.cs" />
<Compile Include="Controllers\PackagingServicesController.cs" />
<Compile Include="Controllers\GalleryController.cs" />
<Compile Include="DefaultPackagingUpdater.cs" />
<Compile Include="Events\ExtensionDisplayEventHandler.cs" />
<Compile Include="Events\IExtensionDisplayEventHandler.cs" />
<Compile Include="Migrations.cs" />
<Compile Include="Models\PackagingSource.cs" />
<Compile Include="Models\UpdatePackageEntry.cs" />
<Compile Include="Permissions.cs" />
<Compile Include="ResourceManifest.cs" />
<Compile Include="Service References\GalleryServer\Reference.cs">
@@ -72,8 +76,11 @@
<DesignTime>True</DesignTime>
<DependentUpon>Reference.datasvcmap</DependentUpon>
</Compile>
<Compile Include="Services\BackgroundPackageUpdateStatus.cs" />
<Compile Include="Services\BackgroundPackageUpdateTask.cs" />
<Compile Include="Services\ExtensionReferenceRepository.cs" />
<Compile Include="Services\FileBaseProjectSystem.cs" />
<Compile Include="Services\FolderUpdater.cs" />
<Compile Include="Services\IPackageBuilder.cs" />
<Compile Include="Services\IPackageInstaller.cs" />
<Compile Include="Services\IPackageManager.cs" />
@@ -84,11 +91,13 @@
<Compile Include="Services\PackageInstaller.cs" />
<Compile Include="Services\PackageManager.cs" />
<Compile Include="Models\PackagingEntry.cs" />
<Compile Include="Services\PackageUpdateManager.cs" />
<Compile Include="Services\PackagingSourceManager.cs" />
<Compile Include="ViewModels\PackagingAddSourceViewModel.cs" />
<Compile Include="ViewModels\PackagingHarvestViewModel.cs" />
<Compile Include="ViewModels\PackagingExtensionsViewModel.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ViewModels\PackagingListViewModel.cs" />
<Compile Include="ViewModels\PackagingSourcesViewModel.cs" />
</ItemGroup>
<ItemGroup>
@@ -145,6 +154,12 @@
<ItemGroup>
<Content Include="web.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\GalleryUpdates\ThemesUpdates.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\GalleryUpdates\ModulesUpdates.cshtml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@@ -0,0 +1,13 @@
using Orchard.Environment.Extensions;
using Orchard.Packaging.Models;
namespace Orchard.Packaging.Services {
public interface IBackgroundPackageUpdateStatus : ISingletonDependency {
PackagesStatusResult Value { get; set; }
}
[OrchardFeature("Gallery.Updates")]
public class BackgroundPackageUpdateStatus : IBackgroundPackageUpdateStatus {
public PackagesStatusResult Value { get; set; }
}
}

View File

@@ -0,0 +1,33 @@
using Orchard.Environment.Extensions;
using Orchard.Tasks;
namespace Orchard.Packaging.Services {
/// <summary>
/// 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.
/// </summary>
[OrchardFeature("Gallery.Updates")]
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());
}
}
}

View File

@@ -0,0 +1,114 @@
using System;
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")]
public class FolderUpdater : IFolderUpdater {
private readonly INotifier _notifier;
public class FolderContent {
public DirectoryInfo Folder { get; set; }
public IEnumerable<string> Files { get; set; }
}
public FolderUpdater(INotifier notifier) {
_notifier = notifier;
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<string>();
GetFolderContent(folder, "", files);
return new FolderContent { Folder = folder, Files = files };
}
private void GetFolderContent(DirectoryInfo folder, string prefix, List<string> 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);
}
}
}
}

View File

@@ -0,0 +1,261 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Orchard.Caching;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.VirtualPath;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Packaging.Models;
using Orchard.Services;
using Orchard.UI.Notify;
namespace Orchard.Packaging.Services {
public interface IPackageUpdateService : IDependency {
PackagesStatusResult GetPackagesStatus(IEnumerable<PackagingSource> sources);
void TriggerRefresh();
void Update(PackagingEntry entry);
void Uninstall(string packageId);
}
[OrchardFeature("Gallery.Updates")]
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<PackagingSource> sources) {
var result = new PackagesStatusResult {
Entries = new List<UpdatePackageEntry>(),
Errors = new List<Exception>()
};
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<UpdatePackageEntry>(),
Errors = new List<Exception>()
};
try {
result.Entries = GetPackagesWorker(packagingSource);
}
catch (Exception e) {
result.Errors = new[] { e };
}
return result;
});
}
private IEnumerable<UpdatePackageEntry> GetPackagesWorker(PackagingSource packagingSource) {
var list = new Dictionary<string, UpdatePackageEntry>(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<string, UpdatePackageEntry> list, string packageId) {
UpdatePackageEntry entry;
if (!list.TryGetValue(packageId, out entry)) {
entry = new UpdatePackageEntry { PackageVersions = new List<PackagingEntry>() };
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));
}
}
}

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
using Orchard.Packaging.Models;
namespace Orchard.Packaging.ViewModels {
public class PackagingListViewModel {
public IEnumerable<UpdatePackageEntry> Entries { get; set; }
public dynamic Pager { get; set; }
}
}

View File

@@ -0,0 +1,92 @@
@using Orchard.Modules.Extensions
@using Orchard.Mvc.Html;
@using Orchard.Packaging.ViewModels;
@using Orchard.Packaging.Services;
@using Orchard.Packaging.Models;
@using Orchard.Environment.Extensions.Models;
@using Orchard.Utility.Extensions;
@model PackagingListViewModel
@{
Style.Require("PackagingAdmin");
Layout.Title = T("Modules").ToString();
}
@functions {
public string InstallAction(PackagingEntry package) {
return Url.Action("Install", "GalleryUpdates", 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"
});
}
}
@if (Model.Entries.Count() <= 0) {
<p>No package updates available.</p>
} else {
<ul class="contentItems">
@foreach (var module in Model.Entries) {
<li>
@{
string iconUrl = @module.NewVersionToInstall.IconUrl;
if (string.IsNullOrWhiteSpace(iconUrl)) {
iconUrl = Href("../../Content/Images/ModuleDefaultIcon.png");
}
}
<div class="iconThumbnail">
<div class="extensionDetails column">
<div class="extensionName">
@if (!string.IsNullOrWhiteSpace(module.NewVersionToInstall.GalleryDetailsUrl)) {
<a href="@module.NewVersionToInstall.GalleryDetailsUrl">
<h2>@module.NewVersionToInstall.Title<span> - @T("Version: {0}", module.NewVersionToInstall.Version)</span></h2>
</a>
} else {
<h2>@module.NewVersionToInstall.Title<span> - @T("Version: {0}", module.NewVersionToInstall.Version)</span></h2>
}
</div>
<div class="related">
@Html.Link(T("Install Latest").Text, InstallAction(module.NewVersionToInstall))@T(" | ")
<a href="@module.NewVersionToInstall.PackageStreamUri">@T("Download")</a>
</div>
<div class="properties">
<p>@(module.NewVersionToInstall.Description == null ? T("(No description").Text : module.NewVersionToInstall.Description)</p>
<ul class="pageStatus">
<li>@T("Last Updated: {0}", module.NewVersionToInstall.LastUpdated)</li>
<li>&nbsp;&#124;&nbsp;@T("Author: {0}", !string.IsNullOrEmpty(module.NewVersionToInstall.Authors) ? module.NewVersionToInstall.Authors : T("Unknown").ToString())</li>
<li>&nbsp;&#124;&nbsp;@T("Downloads: {0}", module.NewVersionToInstall.DownloadCount)</li>
<li>&nbsp;&#124;&nbsp;@T("Website: ")
@if (!string.IsNullOrEmpty(module.NewVersionToInstall.ProjectUrl)) { <a href="@module.NewVersionToInstall.ProjectUrl">@module.NewVersionToInstall.ProjectUrl</a> } else { @T("Unknown").ToString() }
</li>
<li><div>&nbsp;&#124;&nbsp;@T("Rating: ")
<div class="ratings" style="width:@(15 * 5)px" title="@T("Ratings: {0} ({1})", module.NewVersionToInstall.Rating, module.NewVersionToInstall.RatingsCount)">
<div class="score" style="width:@(15 * (module.NewVersionToInstall.Rating))px">&nbsp;</div>
</div>
</div>
</li>
</ul>
</div>
</div>
<div class="extensionThumbnail column">
<img src="@iconUrl" class="thumbnail" alt="module" />
</div>
</div>
</li>}
</ul>
@Display(Model.Pager)
}

View File

@@ -0,0 +1,97 @@
@using Orchard.Modules.Extensions
@using Orchard.Mvc.Html;
@using Orchard.Packaging.ViewModels;
@using Orchard.Packaging.Services;
@using Orchard.Packaging.Models;
@using Orchard.Environment.Extensions.Models;
@using Orchard.Utility.Extensions;
@model PackagingListViewModel
@{
Style.Require("PackagingAdmin");
Layout.Title = T("Themes").ToString();
}
@functions {
public string InstallAction(PackagingEntry package) {
return Url.Action("Install", "GalleryUpdates", 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"
});
}
}
@if (Model.Entries.Count() <= 0) {
<p>No package updates available.</p>
} else {
<ul class="contentItems theme">
@foreach (var theme in Model.Entries) {
<li>
@{
string extensionClass = "iconThumbnail";
string iconUrl = @theme.NewVersionToInstall.IconUrl;
if (!string.IsNullOrWhiteSpace(@theme.NewVersionToInstall.FirstScreenshot)) {
iconUrl = @theme.NewVersionToInstall.FirstScreenshot;
extensionClass = "screenshotThumbnail";
} else if (string.IsNullOrWhiteSpace(iconUrl)) {
iconUrl = Href("../../Content/Images/imagePlaceholder.png");
extensionClass = "screenshotThumbnail";
}
}
<div class="@extensionClass">
<div class="extensionDetails column">
<div class="extensionName">
@if (!string.IsNullOrWhiteSpace(theme.NewVersionToInstall.GalleryDetailsUrl)) {
<a href="@theme.NewVersionToInstall.GalleryDetailsUrl">
<h2>@theme.NewVersionToInstall.Title<span> - @T("Version: {0}", theme.NewVersionToInstall.Version)</span></h2>
</a>
} else {
<h2>@theme.NewVersionToInstall.Title<span> - @T("Version: {0}", theme.NewVersionToInstall.Version)</span></h2>
}
</div>
<div class="related">
@Html.Link(T("Install Latest").Text, InstallAction(theme.NewVersionToInstall))@T(" | ")
<a href="@theme.NewVersionToInstall.PackageStreamUri">@T("Download")</a>
</div>
<div class="properties">
<p>@(theme.NewVersionToInstall.Description == null ? T("(No description").Text : theme.NewVersionToInstall.Description)</p>
<ul class="pageStatus">
<li>@T("Last Updated: {0}", theme.NewVersionToInstall.LastUpdated)</li>
<li>&nbsp;&#124;&nbsp;@T("Author: {0}", !string.IsNullOrEmpty(theme.NewVersionToInstall.Authors) ? theme.NewVersionToInstall.Authors : T("Unknown").ToString())</li>
<li>&nbsp;&#124;&nbsp;@T("Downloads: {0}", theme.NewVersionToInstall.DownloadCount)</li>
<li>&nbsp;&#124;&nbsp;@T("Website: ")
@if (!string.IsNullOrEmpty(theme.NewVersionToInstall.ProjectUrl)) { <a href="@theme.NewVersionToInstall.ProjectUrl">@theme.NewVersionToInstall.ProjectUrl</a> } else { @T("Unknown").ToString() }
</li>
<li><div>&nbsp;&#124;&nbsp;@T("Rating: ")
<div class="ratings" style="width:@(15 * 5)px" title="@T("Ratings: {0} ({1})", theme.NewVersionToInstall.Rating, theme.NewVersionToInstall.RatingsCount)">
<div class="score" style="width:@(15 * (theme.NewVersionToInstall.Rating))px">&nbsp;</div>
</div>
</div>
</li>
</ul>
</div>
</div>
<div class="extensionThumbnail column">
<img src="@iconUrl" class="thumbnail" alt="theme" />
</div>
</div>
</li>}
</ul>
@Display(Model.Pager)
}