Gallery: publish, browse, install, and download themes. Gallery displays theme thumbnail.

--HG--
branch : dev
This commit is contained in:
Dave Reed
2010-10-05 21:04:17 -07:00
parent 16feca6be1
commit a599596d44
13 changed files with 183 additions and 42 deletions

View File

@@ -11,9 +11,11 @@ namespace Orchard.Packaging {
public void GetNavigation(NavigationBuilder builder) {
builder.Add(T("Gallery"), "5", menu => menu
.Add(T("Browse Gallery"), "1.0", item => item
.Action("Index", "Gallery", new { area = "Orchard.Packaging" }))
.Add(T("Gallery Feeds"), "2.0", item => item
.Add(T("Browse Modules"), "1.0", item => item
.Action("ModulesIndex", "Gallery", new { area = "Orchard.Packaging" }))
.Add(T("Browse Themes"), "2.0", item => item
.Action("ThemesIndex", "Gallery", new { area = "Orchard.Packaging" }))
.Add(T("Gallery Feeds"), "3.0", item => item
.Action("Sources", "Gallery", new { area = "Orchard.Packaging" })));
}
}

View File

@@ -63,7 +63,10 @@ namespace Orchard.Packaging.Commands {
Context.Output.WriteLine(T("Success"));
}
catch (WebException webException) {
var text = new StreamReader(webException.Response.GetResponseStream()).ReadToEnd();
string text = "";
if (webException.Response != null) {
text = new StreamReader(webException.Response.GetResponseStream()).ReadToEnd();
}
throw new ApplicationException(text);
}
}

View File

@@ -34,10 +34,14 @@ namespace Orchard.Packaging.Controllers {
Localizer T { get; set; }
public ActionResult Index() {
public ActionResult ModulesIndex() {
return Modules(Guid.Empty);
}
public ActionResult ThemesIndex() {
return Themes(Guid.Empty);
}
public ActionResult Sources() {
return View("Sources", new PackagingSourcesViewModel {
Sources = _packagingSourceManager.GetSources(),
@@ -47,7 +51,7 @@ namespace Orchard.Packaging.Controllers {
public ActionResult Remove(Guid id) {
_packagingSourceManager.RemoveSource(id);
_notifier.Information(T("The feed has been removed successfully."));
Update();
Update(null);
return RedirectToAction("Sources");
}
@@ -90,7 +94,7 @@ namespace Orchard.Packaging.Controllers {
_packagingSourceManager.AddSource(new PackagingSource { Id = Guid.NewGuid(), FeedUrl = url, FeedTitle = title });
_notifier.Information(T("The feed has been added successfully."));
Update();
Update(null);
return RedirectToAction("Sources");
}
catch ( Exception exception ) {
@@ -102,18 +106,28 @@ namespace Orchard.Packaging.Controllers {
public ActionResult Modules(Guid? sourceId) {
var selectedSource = _packagingSourceManager.GetSources().Where(s => s.Id == sourceId).FirstOrDefault();
return View("Modules", new PackagingModulesViewModel {
Modules = _packagingSourceManager.GetModuleList(selectedSource),
Modules = _packagingSourceManager.GetModuleList(selectedSource).Where(p => p.SyndicationItem.Categories.All(c => c.Name == "Orchard Module" || c.Name != "Orchard Theme")),
Sources = _packagingSourceManager.GetSources().OrderBy(s => s.FeedTitle),
SelectedSource = selectedSource
});
}
public ActionResult Update() {
public ActionResult Themes(Guid? sourceId) {
var selectedSource = _packagingSourceManager.GetSources().Where(s => s.Id == sourceId).FirstOrDefault();
return View("Themes", new PackagingModulesViewModel {
Modules = _packagingSourceManager.GetModuleList(selectedSource).Where(p => p.SyndicationItem.Categories.Any(c => c.Name == "Orchard Theme")),
Sources = _packagingSourceManager.GetSources().OrderBy(s => s.FeedTitle),
SelectedSource = selectedSource
});
}
public ActionResult Update(string cameFrom) {
_packagingSourceManager.UpdateLists();
_notifier.Information(T("List of available modules and themes is updated."));
return RedirectToAction("Index");
return RedirectToAction(cameFrom == "Themes" ? "ThemesIndex" : "ModulesIndex");
}
public ActionResult Harvest(string extensionName, string feedUrl) {
@@ -147,16 +161,16 @@ namespace Orchard.Packaging.Controllers {
_packageManager.Push(packageData, model.FeedUrl, model.User, model.Password);
_notifier.Information(T("Harvested {0} and published onto {1}", model.ExtensionName, model.FeedUrl));
Update();
Update(null);
return RedirectToAction("Harvest", new { model.ExtensionName, model.FeedUrl });
}
public ActionResult Install(string syndicationId) {
public ActionResult Install(string syndicationId, string cameFrom) {
var packageData = _packageManager.Download(syndicationId);
_packageManager.Install(packageData.PackageStream);
_notifier.Information(T("Installed module"));
return RedirectToAction("Modules");
return RedirectToAction(cameFrom == "Themes" ? "ThemesIndex" : "ModulesIndex");
}
}
}

View File

@@ -121,6 +121,9 @@
<ItemGroup>
<Content Include="Web.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Gallery\Themes.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

@@ -53,7 +53,7 @@ namespace Orchard.Packaging.Services {
}
public IEnumerable<PackagingEntry> GetModuleList(PackagingSource packagingSource = null) {
IEnumerable<PackagingEntry> packageInfos = ( packagingSource == null ? GetSources() : new[] { packagingSource } )
return (packagingSource == null ? GetSources() : new[] {packagingSource})
.SelectMany(
source =>
Bind(ParseFeed(GetModuleListForSource(source)),
@@ -64,11 +64,8 @@ namespace Orchard.Packaging.Services {
Source = source,
SyndicationFeed = feed,
SyndicationItem = item,
PackageStreamUri = item.Links.Single().GetAbsoluteUri().AbsoluteUri,
}))));
return packageInfos.ToArray();
PackageStreamUri = item.Links.Where(l => String.IsNullOrEmpty(l.RelationshipType)).FirstOrDefault().GetAbsoluteUri().AbsoluteUri,
})))).ToArray();
}
private string GetModuleListForSource(PackagingSource source) {

View File

@@ -1,9 +1,9 @@
@model Orchard.Packaging.ViewModels.PackagingModulesViewModel
@{ Style.Require("PackagingAdmin"); }
<h1>@Html.TitleForPage(T("Browse Gallery").ToString())</h1>
<h1>@Html.TitleForPage(T("Browse Gallery - Modules").ToString())</h1>
<div class="manage">@Html.ActionLink(T("Refresh").ToString(), "Update", new object{}, new { @class = "button primaryAction" })</div>
<div class="manage">@Html.ActionLink(T("Refresh").ToString(), "Update", new { cameFrom = "Themes" }, new { @class = "button primaryAction" })</div>
@using ( Html.BeginFormAntiForgeryPost(Url.Action("Modules", "Gallery")) ) {
<fieldset class="bulk-actions">
<label for="filterResults" class="bulk-filter">@T("Feed:")</label>
@@ -27,7 +27,7 @@
</div>
<div class="related">
@Html.ActionLink(T("Install").ToString(), "Install", new RouteValueDictionary {{"SyndicationId",item.SyndicationItem.Id}})@T(" | ")
@Html.ActionLink(T("Install").ToString(), "Install", new RouteValueDictionary {{"SyndicationId",item.SyndicationItem.Id},{"cameFrom", "Modules"}})@T(" | ")
<a href="@item.PackageStreamUri">@T("Download")</a>
</div>
@@ -40,5 +40,4 @@
</div>
</li>}
</ul>
}
}

View File

@@ -0,0 +1,49 @@
@model Orchard.Packaging.ViewModels.PackagingModulesViewModel
@{
Style.Require("PackagingAdmin");
Style.Require("ThemesAdmin");
}
<h1>@Html.TitleForPage(T("Browse Gallery - Themes").ToString())</h1>
<div class="manage">@Html.ActionLink(T("Refresh").ToString(), "Update", new { cameFrom = "Themes" }, new { @class = "button primaryAction" })</div>
@using ( Html.BeginFormAntiForgeryPost(Url.Action("Themes", "Gallery")) ) {
<fieldset class="bulk-actions">
<label for="filterResults" class="bulk-filter">@T("Feed:")</label>
<select id="sourceId" name="sourceId">
@Html.SelectOption("", Model.SelectedSource == null, T("Any (show all feeds)").ToString())
@foreach (var source in Model.Sources) {
Html.SelectOption(source.Id, Model.SelectedSource != null && Model.SelectedSource.Id == source.Id, source.FeedTitle);
}
</select>
<button type="submit">@T("Apply")</button>
</fieldset>
}
@if (Model.Modules.Count() > 0) {
<ul class="templates">
@foreach (var item in Model.Modules) {
<li>
@{
var author = item.SyndicationItem.Authors.Any() ? String.Join(", ", item.SyndicationItem.Authors.Select(a => a.Name)) : T("Unknown").Text;
var title = item.SyndicationItem.Title == null ? T("(No title)").Text : item.SyndicationItem.Title.Text;
var thumbnail = item.SyndicationItem.Links.Where(l=>l.RelationshipType=="thumbnail").Select(l=>l.Uri.ToString()).FirstOrDefault();
}
<div>
<h3>@title</h3>
@if(!String.IsNullOrEmpty(thumbnail)) {
@Html.Image(thumbnail, Html.Encode(title), null)
}
<br/>
@Html.ActionLink(T("Install").ToString(), "Install", new RouteValueDictionary {{"SyndicationId",item.SyndicationItem.Id},{"cameFrom", "Themes" }}, new Dictionary<string, object> { { "class", "button" } })
<a class="button" href="@item.PackageStreamUri">@T("Download")</a>
<h5>@T("By") @author</h5>
<p>
@T("Version: {0}", "1.0")<br />
@(item.SyndicationItem.Summary == null ? T("(No description").Text : item.SyndicationItem.Summary.Text)<br />
</p>
</div>
</li>
}
</ul>
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -1,19 +1,30 @@
using System.IO;
using System;
using System.IO;
using System.Web.Mvc;
namespace PackageIndexReferenceImplementation.Controllers {
public class StreamResult : ActionResult {
public string ContentType { get; set; }
public Stream Stream { get; set; }
public DateTime? LastModifiedDate { get; set; }
public StreamResult(string contentType, Stream stream) {
public StreamResult(string contentType, Stream stream) : this(contentType, stream, null) {
}
public StreamResult(string contentType, Stream stream, DateTime? lastModified) {
ContentType = contentType;
Stream = stream;
LastModifiedDate = lastModified;
}
public override void ExecuteResult(ControllerContext context) {
context.HttpContext.Response.ContentType = ContentType;
Stream.CopyTo(context.HttpContext.Response.OutputStream);
var response = context.HttpContext.Response;
response.ContentType = ContentType;
if (LastModifiedDate.HasValue) {
response.Cache.SetLastModified(LastModifiedDate.Value);
response.Cache.SetCacheability(System.Web.HttpCacheability.Public);
}
Stream.CopyTo(response.OutputStream);
}
}
}

View File

@@ -45,7 +45,7 @@ namespace PackageIndexReferenceImplementation.Controllers {
var password = Encoding.UTF8.GetString(Convert.FromBase64String(HttpContext.Request.Headers["Password"]));
if ( !FormsAuthentication.Authenticate(user, password) ) {
throw new AuthenticationException("This credentials are not valid fo this action.");
throw new AuthenticationException("This credentials are not valid for this action.");
}
var utcNowDateString = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd");
@@ -74,7 +74,8 @@ namespace PackageIndexReferenceImplementation.Controllers {
}
private string UpdateSyndicationItem(PackageProperties packageProperties, SyndicationItem item) {
if (!string.IsNullOrEmpty(packageProperties.Category)) {
if (!string.IsNullOrEmpty(packageProperties.Creator)) {
item.Authors.Clear();
//parse package.PackageProperties.Creator into email-style authors
item.Authors.Add(new SyndicationPerson { Name = packageProperties.Creator });
@@ -85,6 +86,11 @@ namespace PackageIndexReferenceImplementation.Controllers {
item.Categories.Add(new SyndicationCategory(packageProperties.Category));
}
var contentType = packageProperties.ContentType;
if (!item.Categories.Any(c => c.Name == contentType)) {
item.Categories.Add(new SyndicationCategory(contentType));
}
if (packageProperties.Modified.HasValue) {
item.LastUpdatedTime = new DateTimeOffset(packageProperties.Modified.Value);
}
@@ -97,15 +103,16 @@ namespace PackageIndexReferenceImplementation.Controllers {
item.Summary = new TextSyndicationContent(packageProperties.Description);
}
if (!string.IsNullOrEmpty(packageProperties.Title)) {
item.Title = new TextSyndicationContent(packageProperties.Title);
}
var mediaIdentifier = packageProperties.Identifier + "-" + packageProperties.Version + ".zip";
var mediaUrl = Url.Action("Resource", "Media", new RouteValueDictionary { { "Id", mediaIdentifier }, { "ContentType", "application/x-package" } });
item.Links.Clear();
item.Links.Add(new SyndicationLink(new Uri(HostBaseUri(), new Uri(mediaUrl, UriKind.Relative))));
if (contentType == "Orchard Theme") {
var previewUrl = Url.Action("PreviewTheme", "Media", new RouteValueDictionary { { "Id", mediaIdentifier }, { "ContentType", "application/x-package" } });
item.Links.Add(new SyndicationLink(new Uri(HostBaseUri(), new Uri(previewUrl, UriKind.Relative)), "thumbnail", null, null, 0));
}
return mediaIdentifier;
}

View File

@@ -1,23 +1,49 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Web;
using System.Web.Hosting;
using System.Web.Mvc;
using PackageIndexReferenceImplementation.Services;
namespace PackageIndexReferenceImplementation.Controllers
{
public class MediaController : Controller
{
namespace PackageIndexReferenceImplementation.Controllers {
public class MediaController : Controller {
private readonly MediaStorage _mediaStorage;
public MediaController() {
_mediaStorage = new MediaStorage();
}
public ActionResult Resource(string id, string contentType)
{
public ActionResult Resource(string id, string contentType) {
return new StreamResult(contentType, _mediaStorage.GetMedia(id + ":" + contentType));
}
public ActionResult PreviewTheme(string id, string contentType) {
var stream = _mediaStorage.GetMedia(id + ":" + contentType);
var package = Package.Open(stream, FileMode.Open, FileAccess.Read);
if (package.PackageProperties.ContentType != "Orchard Theme") {
return new HttpNotFoundResult();
}
var themeName = package.PackageProperties.Identifier;
var previewUri = new Uri("/" + themeName + "/Theme.png", UriKind.Relative);
Stream previewStream;
DateTime lastModified;
if (package.PartExists(previewUri)) {
lastModified = _mediaStorage.GetLastModifiedDate(id);
previewStream = package.GetPart(new Uri("/" + themeName + "/Theme.png", UriKind.Relative)).GetStream();
}
else {
var defaultPreviewPath = HostingEnvironment.MapPath("~/Content/DefaultThemePreview.png");
if (defaultPreviewPath == null || !System.IO.File.Exists(defaultPreviewPath)) {
return new HttpNotFoundResult();
}
lastModified = System.IO.File.GetLastWriteTimeUtc(defaultPreviewPath);
previewStream = System.IO.File.Open(defaultPreviewPath, FileMode.Open, FileAccess.Read);
}
return new StreamResult("image/png", previewStream, lastModified);
}
}
}

View File

@@ -0,0 +1,20 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageIndexReferenceImplementation", "PackageIndexReferenceImplementation.csproj", "{8A4E42CE-79F8-4BE2-8B1E-A6B83432123B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8A4E42CE-79F8-4BE2-8B1E-A6B83432123B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8A4E42CE-79F8-4BE2-8B1E-A6B83432123B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8A4E42CE-79F8-4BE2-8B1E-A6B83432123B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8A4E42CE-79F8-4BE2-8B1E-A6B83432123B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using System.Linq;
using System.Web.Hosting;
@@ -25,6 +26,15 @@ namespace PackageIndexReferenceImplementation.Services {
return new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
public DateTime GetLastModifiedDate(string identifier) {
if (!Directory.Exists(HostingEnvironment.MapPath("~/App_Data/Media")))
Directory.CreateDirectory(HostingEnvironment.MapPath("~/App_Data/Media"));
var safeIdentifier = GetSafeIdentifier(identifier);
var filePath = HostingEnvironment.MapPath("~/App_Data/Media/" + safeIdentifier);
return File.GetLastWriteTimeUtc(filePath);
}
static string GetSafeIdentifier(string identifier) {
var invalidFileNameChars = Path.GetInvalidFileNameChars().Concat(Path.GetInvalidPathChars()).Distinct();
var safeIdentifier = identifier.Replace("^", string.Format("^{0:X2}", (int)'^'));