" %>
-<%@ Import Namespace="Orchard.Mvc.Html"%>
-<%: Html.TitleForPage(T("Packages").ToString()) %>
-<%:Html.ActionLink("Update List", "Update") %> • <%:Html.ActionLink("Edit Sources", "Sources") %>
-<%foreach (var item in Model.Modules) {%>- <%:item.AtomEntry.Title %>
<%
- }%>
+<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %>
+<%@ Import Namespace="Orchard.Mvc.Html" %>
+
+ <%: Html.TitleForPage(T("Packaging").ToString(), T("Browse Packages").ToString())%>
+ <%: Html.Partial("_Subnav") %>
+<%:Html.ActionLink("Update List", "Update") %>
+
+ <%foreach (var item in Model.Modules) {%>-
+ <%:item.SyndicationItem.Title.Text%> [<%:Html.ActionLink("Install", "Install", new RouteValueDictionary {{"SyndicationId",item.SyndicationItem.Id}})%>]
+
<%
+ }%>
diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/Sources.ascx b/src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/Sources.ascx
index f5cf543f9..3eaa9f6cb 100644
--- a/src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/Sources.ascx
+++ b/src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/Sources.ascx
@@ -1,7 +1,9 @@
-<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %>
+<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %>
<%@ Import Namespace="Orchard.Mvc.Html" %>
- <%: Html.TitleForPage(T("Packages").ToString()) %>
+ <%: Html.TitleForPage(T("Packaging").ToString(), T("Edit Sources").ToString())%>
+ <%: Html.Partial("_Subnav") %>
+
<%foreach (var item in Model.Sources) {%>-
<%:Html.Link(item.FeedUrl, item.FeedUrl)%>
<%
diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/_Subnav.ascx b/src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/_Subnav.ascx
new file mode 100644
index 000000000..143bf7e19
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/_Subnav.ascx
@@ -0,0 +1,8 @@
+<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
+
+ <%:Html.ActionLink("Browse Repository Packages", "Index") %>
+ •
+ <%:Html.ActionLink("Harvest Local Packages", "Harvest") %>
+ •
+ <%:Html.ActionLink("Edit Repository Sources", "Sources") %>
+
diff --git a/src/Orchard/Commands/CommandContext.cs b/src/Orchard/Commands/CommandContext.cs
index 8f1ed039f..35ae441d3 100644
--- a/src/Orchard/Commands/CommandContext.cs
+++ b/src/Orchard/Commands/CommandContext.cs
@@ -7,9 +7,12 @@ namespace Orchard.Commands {
public class CommandContext {
public TextReader Input { get; set; }
public TextWriter Output { get; set; }
+
+
public string Command { get; set; }
public IEnumerable Arguments { get; set; }
public IDictionary Switches { get; set; }
+
public CommandDescriptor CommandDescriptor { get; set; }
}
}
diff --git a/src/Tools/PackageIndexReferenceImplementation/Controllers/Artifacts/AtomFeedResult.cs b/src/Tools/PackageIndexReferenceImplementation/Controllers/Artifacts/AtomFeedResult.cs
new file mode 100644
index 000000000..beac61c0f
--- /dev/null
+++ b/src/Tools/PackageIndexReferenceImplementation/Controllers/Artifacts/AtomFeedResult.cs
@@ -0,0 +1,21 @@
+using System.ServiceModel.Syndication;
+using System.Web.Mvc;
+using System.Xml;
+
+namespace PackageIndexReferenceImplementation.Controllers.Artifacts {
+ public class AtomFeedResult : ActionResult {
+ public SyndicationFeed Feed { get; set; }
+
+ public AtomFeedResult(SyndicationFeed feed) {
+ Feed = feed;
+ }
+
+ public override void ExecuteResult(ControllerContext context) {
+ context.HttpContext.Response.ContentType = "application/atom+xml";
+ using (var writer = XmlWriter.Create(context.HttpContext.Response.OutputStream)) {
+ var formatter = new Atom10FeedFormatter(Feed);
+ formatter.WriteTo(writer);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Tools/PackageIndexReferenceImplementation/Controllers/Artifacts/AtomItemResult.cs b/src/Tools/PackageIndexReferenceImplementation/Controllers/Artifacts/AtomItemResult.cs
new file mode 100644
index 000000000..cfc02c5c5
--- /dev/null
+++ b/src/Tools/PackageIndexReferenceImplementation/Controllers/Artifacts/AtomItemResult.cs
@@ -0,0 +1,27 @@
+using System.ServiceModel.Syndication;
+using System.Web.Mvc;
+using System.Xml;
+
+namespace PackageIndexReferenceImplementation.Controllers.Artifacts {
+ public class AtomItemResult : ActionResult {
+ public string Status { get; set; }
+ public string Location { get; set; }
+ public SyndicationItem Item { get; set; }
+
+ public AtomItemResult(string status, string location, SyndicationItem item) {
+ Status = status;
+ Location = location;
+ Item = item;
+ }
+
+ public override void ExecuteResult(ControllerContext context) {
+ context.HttpContext.Response.Status = Status;
+ context.HttpContext.Response.RedirectLocation = Location;
+ context.HttpContext.Response.ContentType = "application/atom+xml;type=entry";
+ using (var writer = XmlWriter.Create(context.HttpContext.Response.OutputStream)) {
+ var formatter = new Atom10ItemFormatter(Item);
+ formatter.WriteTo(writer);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Tools/PackageIndexReferenceImplementation/Controllers/Artifacts/ContentTypeAttribute.cs b/src/Tools/PackageIndexReferenceImplementation/Controllers/Artifacts/ContentTypeAttribute.cs
new file mode 100644
index 000000000..fb29f2bca
--- /dev/null
+++ b/src/Tools/PackageIndexReferenceImplementation/Controllers/Artifacts/ContentTypeAttribute.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Reflection;
+using System.Web.Mvc;
+
+namespace PackageIndexReferenceImplementation.Controllers.Artifacts {
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class ContentTypeAttribute : ActionMethodSelectorAttribute {
+ public ContentTypeAttribute(string contentType) {
+ ContentType = contentType;
+ }
+
+ public string ContentType { get; set; }
+
+ public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) {
+ return controllerContext.HttpContext.Request.ContentType.StartsWith(ContentType);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Tools/PackageIndexReferenceImplementation/Controllers/Artifacts/XmlBodyAttribute.cs b/src/Tools/PackageIndexReferenceImplementation/Controllers/Artifacts/XmlBodyAttribute.cs
new file mode 100644
index 000000000..aba4a0d81
--- /dev/null
+++ b/src/Tools/PackageIndexReferenceImplementation/Controllers/Artifacts/XmlBodyAttribute.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Web.Mvc;
+using System.Xml.Linq;
+
+namespace PackageIndexReferenceImplementation.Controllers.Artifacts {
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class XmlBodyAttribute : ActionFilterAttribute {
+ public override void OnActionExecuting(ActionExecutingContext filterContext) {
+ var body = XElement.Load(filterContext.HttpContext.Request.InputStream);
+ filterContext.ActionParameters["body"] = body.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Tools/PackageIndexReferenceImplementation/Controllers/AtomController.cs b/src/Tools/PackageIndexReferenceImplementation/Controllers/AtomController.cs
index b5760bc28..babf1211d 100644
--- a/src/Tools/PackageIndexReferenceImplementation/Controllers/AtomController.cs
+++ b/src/Tools/PackageIndexReferenceImplementation/Controllers/AtomController.cs
@@ -1,42 +1,27 @@
using System;
using System.IO;
using System.IO.Packaging;
-using System.Reflection;
+using System.Linq;
+using System.Security.Policy;
using System.Web.Mvc;
-using System.Xml;
-using System.Xml.Linq;
+using System.Web.Routing;
using System.ServiceModel.Syndication;
+using PackageIndexReferenceImplementation.Controllers.Artifacts;
+using PackageIndexReferenceImplementation.Services;
namespace PackageIndexReferenceImplementation.Controllers {
- public class SyndicationResult : ActionResult {
- public SyndicationFeedFormatter Formatter { get; set; }
-
- public SyndicationResult(SyndicationFeedFormatter formatter) {
- Formatter = formatter;
- }
-
- public override void ExecuteResult(ControllerContext context) {
- context.HttpContext.Response.ContentType = "application/atom+xml";
- using (var writer = XmlWriter.Create(context.HttpContext.Response.OutputStream)) {
- Formatter.WriteTo(writer);
- }
- }
- }
-
[HandleError]
public class AtomController : Controller {
- public ActionResult Index() {
- var feed = new SyndicationFeed {
- Items = new[] {
- new SyndicationItem {
- Id = "hello",
- Title = new TextSyndicationContent("Orchard.Media", TextSyndicationContentKind.Plaintext),
- LastUpdatedTime = DateTimeOffset.UtcNow
- }
- }
- };
+ private readonly FeedStorage _feedStorage;
+ private readonly MediaStorage _mediaStorage;
- return new SyndicationResult(new Atom10FeedFormatter(feed));
+ public AtomController() {
+ _feedStorage = new FeedStorage();
+ _mediaStorage = new MediaStorage();
+ }
+
+ public ActionResult Index() {
+ return new AtomFeedResult(_feedStorage.GetFeed());
}
[ActionName("Index"), HttpPost, ContentType("application/atom+xml"), XmlBody]
@@ -46,37 +31,68 @@ namespace PackageIndexReferenceImplementation.Controllers {
[ActionName("Index"), HttpPost, ContentType("application/x-package")]
public ActionResult PostPackage() {
+
+ var hostHeader = HttpContext.Request.Headers["Host"];
+ var slugHeader = HttpContext.Request.Headers["Slug"];
+ var utcNowDateString = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd");
+
var package = Package.Open(Request.InputStream, FileMode.Open, FileAccess.Read);
+ var packageProperties = package.PackageProperties;
- return RedirectToAction("Index");
+ var feed = _feedStorage.GetFeed();
+
+ var item = feed.Items.FirstOrDefault(i => i.Id.StartsWith("tag:") && i.Id.EndsWith(":" + packageProperties.Identifier));
+ if (item == null) {
+ item = new SyndicationItem {
+ Id = "tag:" + hostHeader + "," + utcNowDateString + ":" + packageProperties.Identifier
+ };
+ feed.Items = feed.Items.Concat(new[] { item });
+ }
+
+
+ if (!string.IsNullOrEmpty(packageProperties.Category)) {
+ item.Authors.Clear();
+ //parse package.PackageProperties.Creator into email-style authors
+ item.Authors.Add(new SyndicationPerson { Name = packageProperties.Creator });
+ }
+
+ if (!string.IsNullOrEmpty(packageProperties.Category)) {
+ item.Categories.Clear();
+ item.Categories.Add(new SyndicationCategory(packageProperties.Category));
+ }
+
+ if (packageProperties.Modified.HasValue) {
+ item.LastUpdatedTime = new DateTimeOffset(packageProperties.Modified.Value);
+ }
+
+ if (!string.IsNullOrEmpty(packageProperties.Title)) {
+ item.Title = new TextSyndicationContent(packageProperties.Title);
+ }
+
+ if (!string.IsNullOrEmpty(packageProperties.Description)) {
+ 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))));
+
+ Request.InputStream.Seek(0, SeekOrigin.Begin);
+ _mediaStorage.StoreMedia(mediaIdentifier+":application/x-package", Request.InputStream);
+ _feedStorage.StoreFeed(feed);
+
+ return new AtomItemResult("201 Created", null, item);
}
- static XElement Atom(string localName, params XNode[] content) {
- return new XElement(XName.Get(localName, "http://www.w3.org/2005/Atom"), content);
- }
- static XElement Atom(string localName, string value) {
- return new XElement(XName.Get(localName, "http://www.w3.org/2005/Atom"), new XText(value));
- }
- }
-
- [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
- public class ContentTypeAttribute : ActionMethodSelectorAttribute {
- public ContentTypeAttribute(string contentType) {
- ContentType = contentType;
+ private Uri HostBaseUri() {
+ return new Uri("http://" + HttpContext.Request.Headers["Host"]);
}
- public string ContentType { get; set; }
-
- public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) {
- return controllerContext.HttpContext.Request.ContentType.StartsWith(ContentType);
- }
- }
-
- [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
- public class XmlBodyAttribute : ActionFilterAttribute {
- public override void OnActionExecuting(ActionExecutingContext filterContext) {
- var body = XElement.Load(filterContext.HttpContext.Request.InputStream);
- filterContext.ActionParameters["body"] = body.ToString();
- }
}
}
diff --git a/src/Tools/PackageIndexReferenceImplementation/Controllers/MediaController.cs b/src/Tools/PackageIndexReferenceImplementation/Controllers/MediaController.cs
new file mode 100644
index 000000000..612d639fe
--- /dev/null
+++ b/src/Tools/PackageIndexReferenceImplementation/Controllers/MediaController.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Web;
+using System.Web.Mvc;
+using PackageIndexReferenceImplementation.Services;
+
+namespace PackageIndexReferenceImplementation.Controllers
+{
+ public class MediaController : Controller
+ {
+ private readonly MediaStorage _mediaStorage;
+
+ public MediaController() {
+ _mediaStorage = new MediaStorage();
+ }
+
+ public ActionResult Resource(string id, string contentType)
+ {
+ return new StreamResult(contentType, _mediaStorage.GetMedia(id + ":" + contentType));
+ }
+ }
+
+ public class StreamResult : ActionResult {
+ public string ContentType { get; set; }
+ public Stream Stream { get; set; }
+
+ public StreamResult(string contentType, Stream stream) {
+ ContentType = contentType;
+ Stream = stream;
+ }
+
+ public override void ExecuteResult(ControllerContext context) {
+ context.HttpContext.Response.ContentType = ContentType;
+ Stream.CopyTo(context.HttpContext.Response.OutputStream);
+ }
+ }
+}
diff --git a/src/Tools/PackageIndexReferenceImplementation/PackageIndexReferenceImplementation.csproj b/src/Tools/PackageIndexReferenceImplementation/PackageIndexReferenceImplementation.csproj
index 0de509bdd..9b877034e 100644
--- a/src/Tools/PackageIndexReferenceImplementation/PackageIndexReferenceImplementation.csproj
+++ b/src/Tools/PackageIndexReferenceImplementation/PackageIndexReferenceImplementation.csproj
@@ -69,12 +69,19 @@
+
+
+
+
+
Global.asax
+
+
diff --git a/src/Tools/PackageIndexReferenceImplementation/Services/FeedStorage.cs b/src/Tools/PackageIndexReferenceImplementation/Services/FeedStorage.cs
new file mode 100644
index 000000000..e226be531
--- /dev/null
+++ b/src/Tools/PackageIndexReferenceImplementation/Services/FeedStorage.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.ServiceModel.Syndication;
+using System.Web;
+using System.Web.Hosting;
+using System.Xml;
+
+namespace PackageIndexReferenceImplementation.Services {
+ public class FeedStorage {
+
+ public SyndicationFeed GetFeed() {
+ var formatter = new Atom10FeedFormatter();
+ var feedPath = HostingEnvironment.MapPath("~/App_Data/Feed.xml");
+ if (!File.Exists(feedPath)) {
+ return new SyndicationFeed();
+ }
+ using (var reader = XmlReader.Create(feedPath)) {
+ formatter.ReadFrom(reader);
+ return formatter.Feed;
+ }
+ }
+
+ public void StoreFeed(SyndicationFeed feed) {
+ var formatter = new Atom10FeedFormatter(feed);
+ var feedPath = HostingEnvironment.MapPath("~/App_Data/Feed.xml");
+ using (var writer = XmlWriter.Create(feedPath)) {
+ formatter.WriteTo(writer);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Tools/PackageIndexReferenceImplementation/Services/MediaStorage.cs b/src/Tools/PackageIndexReferenceImplementation/Services/MediaStorage.cs
new file mode 100644
index 000000000..1728c3db4
--- /dev/null
+++ b/src/Tools/PackageIndexReferenceImplementation/Services/MediaStorage.cs
@@ -0,0 +1,36 @@
+using System.IO;
+using System.Linq;
+using System.Web.Hosting;
+
+namespace PackageIndexReferenceImplementation.Services {
+ public class MediaStorage {
+ public void StoreMedia(string identifier, Stream data) {
+ 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);
+ using (var destination = new FileStream(filePath, FileMode.Create, FileAccess.Write)) {
+ data.CopyTo(destination);
+ }
+ }
+
+ public Stream GetMedia(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 new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
+ }
+
+ static string GetSafeIdentifier(string identifier) {
+ var invalidFileNameChars = Path.GetInvalidFileNameChars().Concat(Path.GetInvalidPathChars()).Distinct();
+ var safeIdentifier = identifier.Replace("^", string.Format("^{0:X2}", (int)'^'));
+ foreach (var ch in invalidFileNameChars) {
+ safeIdentifier = safeIdentifier.Replace(new string(ch, 1), string.Format("^{0:X2}", (int)ch));
+ }
+ return safeIdentifier;
+ }
+ }
+}
\ No newline at end of file