Continued work on package prototyping

Renamed and organized some components
Rough implementation of install/expand (currently to alternate location to avoid overwrites)

--HG--
branch : dev
This commit is contained in:
Louis DeJardin
2010-07-06 18:56:55 -07:00
parent 25c35ece69
commit 5c45c0d239
25 changed files with 802 additions and 185 deletions

View File

@@ -42,5 +42,7 @@ namespace Orchard.DevTools.Controllers {
model.Results = writer.ToString();
return View("Execute", model);
}
}
}

View File

@@ -45,6 +45,7 @@
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Entity" />
@@ -63,7 +64,9 @@
<Reference Include="System.EnterpriseServices" />
<Reference Include="System.Web.Mobile" />
<Reference Include="System.Xml.Linq" />
<Reference Include="WindowsBase" />
<Reference Include="WindowsBase">
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="AdminMenu.cs" />
@@ -73,9 +76,11 @@
<Compile Include="Models\ModuleFeature.cs" />
<Compile Include="Packaging\Commands\PackagingCommands.cs" />
<Compile Include="Packaging\Controllers\PackagingController.cs" />
<Compile Include="Packaging\Services\PackageExpander.cs" />
<Compile Include="Packaging\Services\PackageBuilder.cs" />
<Compile Include="Packaging\Services\PackageRepository.cs" />
<Compile Include="Packaging\ViewModels\PackagingIndexViewModel.cs" />
<Compile Include="Packaging\Services\PackageManager.cs" />
<Compile Include="Packaging\Services\PackageSourceManager.cs" />
<Compile Include="Packaging\ViewModels\PackagingModulesViewModel.cs" />
<Compile Include="ViewModels\FeaturesViewModel.cs" />
<Compile Include="Models\Module.cs" />
<Compile Include="Permissions.cs" />
@@ -94,6 +99,8 @@
<Content Include="styles\jquery.switchable.css" />
<Content Include="Views\Admin\Add.ascx" />
<Content Include="Views\Admin\Index.ascx" />
<Content Include="Views\Packaging\_Subnav.ascx" />
<Content Include="Views\Packaging\Harvest.ascx" />
<Content Include="Views\Packaging\Sources.ascx" />
<Content Include="Views\Packaging\Modules.ascx" />
<Content Include="Web.config" />

View File

@@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Hosting;
using Orchard.Commands;
using Orchard.Environment.Extensions;
using Orchard.Modules.Packaging.Services;
@@ -12,55 +8,45 @@ using Orchard.Modules.Packaging.Services;
namespace Orchard.Modules.Packaging.Commands {
[OrchardFeature("Orchard.Modules.Packaging")]
public class PackagingCommands : DefaultOrchardCommandHandler {
private readonly IExtensionManager _extensionManager;
private readonly IPackageBuilder _packageBuilder;
private readonly IPackageManager _packageManager;
public PackagingCommands(IPackageBuilder packageBuilder) {
public PackagingCommands(IExtensionManager extensionManager, IPackageBuilder packageBuilder, IPackageManager packageManager) {
_extensionManager = extensionManager;
_packageBuilder = packageBuilder;
_packageManager = packageManager;
}
[CommandHelp("harvest <moduleName>\r\n\t" + "Package a module into a distributable")]
[CommandName("harvest")]
public void PackageCreate(string moduleName) {
var stream = _packageBuilder.Create(moduleName);
if (stream.CanSeek)
stream.Seek(0, SeekOrigin.Begin);
var packageData = _packageManager.Harvest(moduleName);
if (packageData.PackageStream.CanSeek)
packageData.PackageStream.Seek(0, SeekOrigin.Begin);
using(var fileStream = new FileStream(HostingEnvironment.MapPath("~/Modules/" + moduleName + ".zip"), FileMode.Create, FileAccess.Write)) {
const int chunk = 512;
var dataBuffer = new byte[3*chunk];
var charBuffer = new char[4*chunk + 2];
for (;;) {
var dataCount = stream.Read(dataBuffer, 0, dataBuffer.Length);
if (dataCount <= 0)
return;
fileStream.Write(dataBuffer, 0, dataCount);
var charCount = Convert.ToBase64CharArray(dataBuffer, 0, dataCount, charBuffer, 0);
Context.Output.Write(charBuffer, 0, charCount);
}
const int chunk = 512;
var dataBuffer = new byte[3 * chunk];
var charBuffer = new char[4 * chunk + 2];
for (; ; ) {
var dataCount = packageData.PackageStream.Read(dataBuffer, 0, dataBuffer.Length);
if (dataCount <= 0)
return;
var charCount = Convert.ToBase64CharArray(dataBuffer, 0, dataCount, charBuffer, 0);
Context.Output.Write(charBuffer, 0, charCount);
}
}
[CommandHelp("harvest post <moduleName> <feedUrl>\r\n\t" + "Package a module into a distributable and push it to a feed server.")]
[CommandName("harvest post")]
public void PackageCreate(string moduleName, string feed) {
var stream = _packageBuilder.Create(moduleName);
if (stream.CanSeek)
stream.Seek(0, SeekOrigin.Begin);
public void PackageCreate(string moduleName, string feedUrl) {
var packageData = _packageManager.Harvest(moduleName);
_packageManager.Push(packageData, feedUrl);
var request = WebRequest.Create(feed);
request.Method = "POST";
request.ContentType = "application/x-package";
using (var requestStream = request.GetRequestStream()) {
stream.CopyTo(requestStream);
}
try {
using (var response = request.GetResponse()) {
Context.Output.Write("Success: {0}", response.ResponseUri);
}
_packageManager.Push(packageData, feedUrl);
Context.Output.WriteLine("Success");
}
catch (WebException webException) {
var text = new StreamReader(webException.Response.GetResponseStream()).ReadToEnd();

View File

@@ -1,46 +1,126 @@
using System;
using System.IO;
using System.Linq;
using System.Web.Mvc;
using Orchard.Environment.Extensions;
using Orchard.Localization;
using Orchard.Modules.Packaging.Services;
using Orchard.Modules.Packaging.ViewModels;
using Orchard.Themes;
using Orchard.UI.Admin;
using Orchard.UI.Notify;
namespace Orchard.Modules.Packaging.Controllers {
[Admin, Themed, OrchardFeature("Orchard.Modules.Packaging")]
public class PackagingController : Controller {
private readonly IPackageRepository _packageRepository;
private readonly IPackageManager _packageManager;
private readonly IPackageSourceManager _packageSourceManager;
private readonly IExtensionManager _extensionManager;
private readonly INotifier _notifier;
public PackagingController(IPackageRepository packageRepository) {
_packageRepository = packageRepository;
public PackagingController(
IPackageManager packageManager,
IPackageSourceManager packageSourceManager,
IExtensionManager extensionManager,
INotifier notifier) {
_packageManager = packageManager;
_packageSourceManager = packageSourceManager;
_extensionManager = extensionManager;
_notifier = notifier;
T = NullLocalizer.Instance;
}
Localizer T { get; set; }
public ActionResult Index() {
return Modules();
}
public ActionResult Modules() {
return View("Modules", new PackagingIndexViewModel {
Modules = _packageRepository.GetModuleList()
});
}
public ActionResult Sources() {
return View("Sources", new PackagingIndexViewModel {
Sources = _packageRepository.GetSources(),
return View("Sources", new PackagingSourcesViewModel {
Sources = _packageSourceManager.GetSources(),
});
}
public ActionResult AddSource(string url) {
_packageRepository.AddSource(new PackageSource { Id = Guid.NewGuid(), FeedUrl = url });
return RedirectToAction("Index");
_packageSourceManager.AddSource(new PackageSource { Id = Guid.NewGuid(), FeedUrl = url });
Update();
return RedirectToAction("Sources");
}
public ActionResult Modules() {
return View("Modules", new PackagingModulesViewModel {
Modules = _packageSourceManager.GetModuleList()
});
}
public ActionResult Update() {
_packageRepository.UpdateLists();
//notifier
_packageSourceManager.UpdateLists();
_notifier.Information(T("List of available modules and themes is updated."));
return RedirectToAction("Index");
}
public ActionResult Harvest(string extensionName, string feedUrl) {
return View("Harvest", new PackagingHarvestViewModel {
ExtensionName = extensionName,
FeedUrl = feedUrl,
Sources = _packageSourceManager.GetSources(),
Extensions = _extensionManager.AvailableExtensions()
});
}
[HttpPost]
public ActionResult Harvest(PackagingHarvestViewModel model) {
model.Sources = _packageSourceManager.GetSources();
model.Extensions = _extensionManager.AvailableExtensions();
var packageData = _packageManager.Harvest(model.ExtensionName);
if (string.IsNullOrEmpty(model.FeedUrl)) {
return new DownloadStreamResult(
packageData.ExtensionName + "-" + packageData.ExtensionVersion + ".zip",
"application/x-package",
packageData.PackageStream);
}
if (!model.Sources.Any(src => src.FeedUrl == model.FeedUrl)) {
ModelState.AddModelError("FeedUrl", T("May only push directly to one of the configured sources.").ToString());
return View("Harvest", model);
}
_packageManager.Push(packageData, model.FeedUrl);
_notifier.Information(T("Harvested {0} and published onto {1}", model.ExtensionName, model.FeedUrl));
Update();
return RedirectToAction("Harvest", new { model.ExtensionName, model.FeedUrl });
}
public ActionResult Install(string syndicationId) {
var packageData = _packageManager.Download(syndicationId);
_packageManager.Install(packageData);
_notifier.Information(T("Installed module"));
return RedirectToAction("Modules");
}
}
public class DownloadStreamResult : ActionResult {
public string FileName { get; set; }
public string ContentType { get; set; }
public Stream Stream { get; set; }
public DownloadStreamResult(string fileName, string contentType, Stream stream) {
FileName = fileName;
ContentType = contentType;
Stream = stream;
}
public override void ExecuteResult(ControllerContext context) {
context.HttpContext.Response.ContentType = ContentType;
context.HttpContext.Response.AddHeader("content-disposition", "attachment; filename=\"" + FileName + "\"");
Stream.Seek(0, SeekOrigin.Begin);
Stream.CopyTo(context.HttpContext.Response.OutputStream);
}
}
}

View File

@@ -11,7 +11,7 @@ using Orchard.FileSystems.WebSite;
namespace Orchard.Modules.Packaging.Services {
public interface IPackageBuilder : IDependency {
Stream Create(string moduleName);
Stream BuildPackage(ExtensionDescriptor extensionDescriptor);
}
[OrchardFeature("Orchard.Modules.Packaging")]
@@ -35,28 +35,26 @@ namespace Orchard.Modules.Packaging.Services {
public XDocument Project { get; set; }
}
public Stream Create(string moduleName) {
var extensionDescriptor = _extensionManager
.AvailableExtensions()
.FirstOrDefault(x => x.Name == moduleName);
return Create(extensionDescriptor);
}
public Stream Create(ExtensionDescriptor extensionDescriptor) {
var projectFile = extensionDescriptor.Name + ".csproj";
public Stream BuildPackage(ExtensionDescriptor extensionDescriptor) {
var context = new CreateContext();
BeginPackage(context);
EstablishPaths(context, _webSiteFolder, extensionDescriptor.Location, extensionDescriptor.Name);
SetCoreProperties(context, extensionDescriptor);
try {
EstablishPaths(context, _webSiteFolder, extensionDescriptor.Location, extensionDescriptor.Name);
SetCoreProperties(context, extensionDescriptor);
if (LoadProject(context, projectFile)) {
EmbedVirtualFile(context, projectFile, System.Net.Mime.MediaTypeNames.Text.Xml);
EmbedProjectFiles(context, "Compile", "Content", "None", "EmbeddedResource");
EmbedReferenceFiles(context);
var projectFile = extensionDescriptor.Name + ".csproj";
if (LoadProject(context, projectFile)) {
EmbedVirtualFile(context, projectFile, System.Net.Mime.MediaTypeNames.Text.Xml);
EmbedProjectFiles(context, "Compile", "Content", "None", "EmbeddedResource");
EmbedReferenceFiles(context);
}
}
finally {
EndPackage(context);
}
EndPackage(context);
return context.Stream;
}

View File

@@ -0,0 +1,179 @@
using System;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.VirtualPath;
using Orchard.FileSystems.WebSite;
namespace Orchard.Modules.Packaging.Services {
public interface IPackageExpander : IDependency {
void ExpandPackage(Stream packageStream);
}
[OrchardFeature("Orchard.Modules.Packaging")]
public class PackageExpander : IPackageExpander {
private const string ContentTypePrefix = "Orchard ";
private readonly IExtensionManager _extensionManager;
private readonly IWebSiteFolder _webSiteFolder;
private readonly IVirtualPathProvider _virtualPathProvider;
public PackageExpander(
IExtensionManager extensionManager,
IWebSiteFolder webSiteFolder,
IVirtualPathProvider virtualPathProvider) {
_extensionManager = extensionManager;
_webSiteFolder = webSiteFolder;
_virtualPathProvider = virtualPathProvider;
}
class ExpandContext {
public Stream Stream { get; set; }
public Package Package { get; set; }
public string ExtensionName { get; set; }
public string ExtensionType { get; set; }
public string TargetPath { get; set; }
public string SourcePath { get; set; }
public XDocument Project { get; set; }
}
public void ExpandPackage(Stream packageStream) {
var context = new ExpandContext();
BeginPackage(context, packageStream);
try {
GetCoreProperties(context);
EstablishPaths(context, _virtualPathProvider);
var projectFile = context.ExtensionName + ".csproj";
if (LoadProject(context, projectFile)) {
ExtractFile(context, projectFile);
ExtractProjectFiles(context, "Compile", "Content", "None", "EmbeddedResource");
ExtractReferenceFiles(context);
}
}
finally {
EndPackage(context);
}
}
private void ExtractFile(ExpandContext context, string relativePath) {
var partUri = PackUriHelper.CreatePartUri(new Uri(context.SourcePath + relativePath, UriKind.Relative));
var packagePart = context.Package.GetPart(partUri);
using (var packageStream = packagePart.GetStream(FileMode.Open, FileAccess.Read)) {
var filePath = Path.Combine(context.TargetPath, relativePath);
var folderPath = Path.GetDirectoryName(filePath);
if (!Directory.Exists(folderPath))
Directory.CreateDirectory(folderPath);
using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read)) {
packageStream.CopyTo(fileStream);
}
}
}
private void ExtractProjectFiles(ExpandContext context, params string[] itemGroupTypes) {
var itemGroups = context.Project
.Elements(Ns("Project"))
.Elements(Ns("ItemGroup"));
foreach (var itemGroupType in itemGroupTypes) {
var includePaths = itemGroups
.Elements(Ns(itemGroupType))
.Attributes("Include")
.Select(x => x.Value);
foreach (var includePath in includePaths) {
ExtractFile(context, includePath);
}
}
}
private void ExtractReferenceFiles(ExpandContext context) {
var entries = context.Project
.Elements(Ns("Project"))
.Elements(Ns("ItemGroup"))
.Elements(Ns("Reference"))
.Select(reference => new {
Include = reference.Attribute("Include"),
HintPath = reference.Element(Ns("HintPath"))
})
.Where(entry => entry.Include != null);
foreach (var entry in entries) {
var assemblyName = new AssemblyName(entry.Include.Value);
var hintPath = entry.HintPath != null ? entry.HintPath.Value : null;
var virtualPath = "bin/" + assemblyName.Name + ".dll";
if (PartExists(context, virtualPath)) {
ExtractFile(context, virtualPath);
}
else if (hintPath != null) {
}
}
}
private bool PartExists(ExpandContext context, string relativePath) {
var projectUri = PackUriHelper.CreatePartUri(new Uri(context.SourcePath + relativePath, UriKind.Relative));
return context.Package.PartExists(projectUri);
}
private XName Ns(string localName) {
return XName.Get(localName, "http://schemas.microsoft.com/developer/msbuild/2003");
}
private static bool LoadProject(ExpandContext context, string relativePath) {
var projectUri = PackUriHelper.CreatePartUri(new Uri(context.SourcePath + relativePath, UriKind.Relative));
if (!context.Package.PartExists(projectUri))
return false;
var part = context.Package.GetPart(projectUri);
using (var stream = part.GetStream(FileMode.Open, FileAccess.Read)) {
context.Project = XDocument.Load(stream);
}
return true;
}
private void BeginPackage(ExpandContext context, Stream packageStream) {
if (packageStream.CanSeek) {
context.Stream = packageStream;
}
else {
context.Stream = new MemoryStream();
packageStream.CopyTo(context.Stream);
}
context.Package = Package.Open(context.Stream, FileMode.Open, FileAccess.Read);
}
private void EndPackage(ExpandContext context) {
context.Package.Close();
}
private void GetCoreProperties(ExpandContext context) {
context.ExtensionName = context.Package.PackageProperties.Identifier;
var contentType = context.Package.PackageProperties.ContentType;
if (contentType.StartsWith(ContentTypePrefix))
context.ExtensionType = contentType.Substring(ContentTypePrefix.Length);
}
private void EstablishPaths(ExpandContext context, IVirtualPathProvider virtualPathProvider) {
context.SourcePath = "\\" + context.ExtensionName + "\\";
if (context.ExtensionType == "Theme") {
context.TargetPath = virtualPathProvider.MapPath("~/Themes-temp/" + context.ExtensionName);
}
else if (context.ExtensionType == "Module") {
context.TargetPath = virtualPathProvider.MapPath("~/Modules-temp/" + context.ExtensionName);
}
else {
throw new ApplicationException("Unknown extension type");
}
}
}
}

View File

@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Net;
using System.Web;
using Orchard.Environment.Extensions;
namespace Orchard.Modules.Packaging.Services {
public class PackageData {
public string ExtensionName { get; set; }
public string ExtensionVersion { get; set; }
public Stream PackageStream { get; set; }
}
public interface IPackageManager : IDependency {
PackageData Harvest(string extensionName);
PackageData Download(string feedItemId);
void Push(PackageData packageData, string feedUrl);
void Install(PackageData packageData);
}
public class PackageManager : IPackageManager {
private readonly IExtensionManager _extensionManager;
private readonly IPackageSourceManager _packageSourceManager;
private readonly IPackageBuilder _packageBuilder;
private readonly IPackageExpander _packageExpander;
public PackageManager(
IExtensionManager extensionManager,
IPackageSourceManager packageSourceManager,
IPackageBuilder packageBuilder,
IPackageExpander packageExpander) {
_extensionManager = extensionManager;
_packageSourceManager = packageSourceManager;
_packageBuilder = packageBuilder;
_packageExpander = packageExpander;
}
public PackageData Harvest(string extensionName) {
var extensionDescriptor = _extensionManager.AvailableExtensions().FirstOrDefault(x => x.Name == extensionName);
if (extensionDescriptor == null)
return null;
return new PackageData {
ExtensionName = extensionDescriptor.Name,
ExtensionVersion = extensionDescriptor.Version,
PackageStream = _packageBuilder.BuildPackage(extensionDescriptor),
};
}
public void Push(PackageData packageData, string feedUrl) {
var request = WebRequest.Create(feedUrl);
request.Method = "POST";
request.ContentType = "application/x-package";
using (var requestStream = request.GetRequestStream()) {
packageData.PackageStream.Seek(0, SeekOrigin.Begin);
packageData.PackageStream.CopyTo(requestStream);
}
using (request.GetResponse()) {
// forces request and disposes results
}
}
public PackageData Download(string feedItemId) {
var entry = _packageSourceManager.GetModuleList().Single(x => x.SyndicationItem.Id == feedItemId);
var request = WebRequest.Create(entry.PackageStreamUri);
using (var response = request.GetResponse()) {
using (var responseStream = response.GetResponseStream()) {
var stream = new MemoryStream();
responseStream.CopyTo(stream);
var package = Package.Open(stream);
try {
return new PackageData {
ExtensionName = package.PackageProperties.Identifier,
ExtensionVersion = package.PackageProperties.Version,
PackageStream = stream
};
}
finally {
package.Close();
}
}
}
}
public void Install(PackageData packageData) {
_packageExpander.ExpandPackage(packageData.PackageStream);
}
}
}

View File

@@ -2,20 +2,22 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel.Syndication;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using Orchard.Environment.Extensions;
using Orchard.FileSystems.AppData;
namespace Orchard.Modules.Packaging.Services {
public interface IPackageRepository : IDependency {
public interface IPackageSourceManager : IDependency {
IEnumerable<PackageSource> GetSources();
void AddSource(PackageSource source);
void RemoveSource(Guid id);
void UpdateLists();
IEnumerable<PackageInfo> GetModuleList();
IEnumerable<PackageEntry> GetModuleList();
}
public class PackageSource {
@@ -23,17 +25,13 @@ namespace Orchard.Modules.Packaging.Services {
public string FeedUrl { get; set; }
}
public class PackageInfo {
public class PackageEntry {
public PackageSource Source { get; set; }
public AtomEntry AtomEntry { get; set; }
public SyndicationFeed SyndicationFeed { get; set; }
public SyndicationItem SyndicationItem { get; set; }
public string PackageStreamUri { get; set; }
}
public class AtomEntry {
public string Id { get; set; }
public string Title { get; set; }
public string Updated { get; set; }
}
static class AtomExtensions {
public static string Atom(this XElement entry, string localName) {
@@ -47,11 +45,11 @@ namespace Orchard.Modules.Packaging.Services {
}
[OrchardFeature("Orchard.Modules.Packaging")]
public class PackageRepository : IPackageRepository {
public class PackageSourceManager : IPackageSourceManager {
private readonly IAppDataFolder _appDataFolder;
private static readonly XmlSerializer _sourceSerializer = new XmlSerializer(typeof(List<PackageSource>), new XmlRootAttribute("Sources"));
public PackageRepository(IAppDataFolder appDataFolder) {
public PackageSourceManager(IAppDataFolder appDataFolder) {
_appDataFolder = appDataFolder;
}
@@ -103,39 +101,39 @@ namespace Orchard.Modules.Packaging.Services {
return AtomExtensions.AtomXName(localName);
}
public IEnumerable<PackageInfo> GetModuleList() {
var packageInfos = GetSources()
.SelectMany(
source =>
Bind(_appDataFolder.ReadFile(GetFeedCachePath(source)),
content =>
XDocument.Parse(content)
.Elements(Atom("feed"))
.Elements(Atom("entry"))
.SelectMany(
element =>
Bind(new AtomEntry {
Id = element.Atom("id"),
Title = element.Atom("title"),
Updated = element.Atom("updated"),
},
atom =>
Unit(new PackageInfo {
Source = source,
AtomEntry = atom,
})))));
return packageInfos.ToArray();
}
static IEnumerable<T> Unit<T>(T t) where T : class {
return t != null ? new[] { t } : Enumerable.Empty<T>();
}
static IEnumerable<T2> Bind<T, T2>(T t, Func<T, IEnumerable<T2>> f) where T : class {
return Unit(t).SelectMany(f);
}
private SyndicationFeed ParseFeed(string content) {
var formatter = new Atom10FeedFormatter<SyndicationFeed>();
formatter.ReadFrom(XmlReader.Create(new StringReader(content)));
return formatter.Feed;
}
public IEnumerable<PackageEntry> GetModuleList() {
var packageInfos = GetSources()
.SelectMany(
source =>
Bind(ParseFeed(_appDataFolder.ReadFile(GetFeedCachePath(source))),
feed =>
feed.Items.SelectMany(
item =>
Unit(new PackageEntry {
Source = source,
SyndicationFeed = feed,
SyndicationItem = item,
PackageStreamUri = item.Links.Single().GetAbsoluteUri().AbsoluteUri,
}))));
return packageInfos.ToArray();
}
}
}

View File

@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Orchard.Modules.Packaging.Services;
namespace Orchard.Modules.Packaging.ViewModels {
public class PackagingIndexViewModel {
public IEnumerable<PackageSource> Sources { get; set; }
public IEnumerable<PackageInfo> Modules { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using Orchard.Environment.Extensions.Models;
using Orchard.Modules.Packaging.Services;
namespace Orchard.Modules.Packaging.ViewModels {
public class PackagingModulesViewModel {
public IEnumerable<PackageEntry> Modules { get; set; }
}
public class PackagingSourcesViewModel {
public IEnumerable<PackageSource> Sources { get; set; }
}
public class PackagingHarvestViewModel {
public IEnumerable<PackageSource> Sources { get; set; }
public IEnumerable<ExtensionDescriptor> Extensions { get; set; }
[Required]
public string ExtensionName { get; set; }
public string FeedUrl { get; set; }
}
}

View File

@@ -0,0 +1,29 @@
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<Orchard.Modules.Packaging.ViewModels.PackagingHarvestViewModel>" %>
<%@ Import Namespace="Orchard.Mvc.Html" %>
<h1>
<%: Html.TitleForPage(T("Packaging").ToString(), T("Harvest Packages").ToString())%></h1>
<%: Html.Partial("_Subnav") %>
<%using (Html.BeginFormAntiForgeryPost()) {%>
<%: Html.ValidationSummary(T("Package creation was unsuccessful. Please correct the errors and try again.").ToString()) %>
<%foreach (var group in Model.Extensions.Where(x => !x.Location.StartsWith("~/Core")).GroupBy(x => x.ExtensionType)) {%>
<fieldset>
<legend>Harvest
<%:group.Key %></legend>
<ul>
<%foreach (var item in group) {%>
<li>
<label>
<%:Html.RadioButtonFor(m=>m.ExtensionName, item.Name, new Dictionary<string, object>{{"id",item.Name}}) %>
<%:item.DisplayName%></label></li><%
}%></ul>
<%} %>
<%: Html.ValidationMessageFor(m => m.ExtensionName)%>
</fieldset>
<fieldset>
<%: Html.LabelFor(m=>m.FeedUrl)%>
<%: Html.DropDownListFor(m => m.FeedUrl, new[]{new SelectListItem{Text="Download",Value=""}}.Concat( Model.Sources.Select(x => new SelectListItem { Text = "Push to " + x.FeedUrl, Value = x.FeedUrl })))%>
<%: Html.ValidationMessageFor(m=>m.FeedUrl) %>
</fieldset>
<input type="submit" value="Harvest" />
<%} %>

View File

@@ -1,7 +1,12 @@
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<Orchard.Modules.Packaging.ViewModels.PackagingIndexViewModel>" %>
<%@ Import Namespace="Orchard.Mvc.Html"%>
<h1><%: Html.TitleForPage(T("Packages").ToString()) %></h1>
<p><%:Html.ActionLink("Update List", "Update") %> &bull; <%:Html.ActionLink("Edit Sources", "Sources") %></p>
<ul><%foreach (var item in Model.Modules) {%><li><%:item.AtomEntry.Title %></li><%
}%></ul>
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<Orchard.Modules.Packaging.ViewModels.PackagingModulesViewModel>" %>
<%@ Import Namespace="Orchard.Mvc.Html" %>
<h1>
<%: Html.TitleForPage(T("Packaging").ToString(), T("Browse Packages").ToString())%></h1>
<%: Html.Partial("_Subnav") %>
<p><%:Html.ActionLink("Update List", "Update") %></p>
<ul>
<%foreach (var item in Model.Modules) {%><li><a href="<%:item.PackageStreamUri%>">
<%:item.SyndicationItem.Title.Text%></a> [<%:Html.ActionLink("Install", "Install", new RouteValueDictionary {{"SyndicationId",item.SyndicationItem.Id}})%>]
</li><%
}%></ul>

View File

@@ -1,7 +1,9 @@
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<Orchard.Modules.Packaging.ViewModels.PackagingIndexViewModel>" %>
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<Orchard.Modules.Packaging.ViewModels.PackagingSourcesViewModel>" %>
<%@ Import Namespace="Orchard.Mvc.Html" %>
<h1>
<%: Html.TitleForPage(T("Packages").ToString()) %></h1>
<%: Html.TitleForPage(T("Packaging").ToString(), T("Edit Sources").ToString())%></h1>
<%: Html.Partial("_Subnav") %>
<ul>
<%foreach (var item in Model.Sources) {%><li>
<%:Html.Link(item.FeedUrl, item.FeedUrl)%></li><%

View File

@@ -0,0 +1,8 @@
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<p>
<%:Html.ActionLink("Browse Repository Packages", "Index") %>
&bull;
<%:Html.ActionLink("Harvest Local Packages", "Harvest") %>
&bull;
<%:Html.ActionLink("Edit Repository Sources", "Sources") %>
</p>

View File

@@ -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<string> Arguments { get; set; }
public IDictionary<string,string> Switches { get; set; }
public CommandDescriptor CommandDescriptor { get; set; }
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -69,12 +69,19 @@
<ItemGroup>
<Compile Include="Controllers\AccountController.cs" />
<Compile Include="Controllers\AtomController.cs" />
<Compile Include="Controllers\Artifacts\AtomFeedResult.cs" />
<Compile Include="Controllers\Artifacts\AtomItemResult.cs" />
<Compile Include="Controllers\Artifacts\ContentTypeAttribute.cs" />
<Compile Include="Controllers\HomeController.cs" />
<Compile Include="Controllers\MediaController.cs" />
<Compile Include="Controllers\Artifacts\XmlBodyAttribute.cs" />
<Compile Include="Global.asax.cs">
<DependentUpon>Global.asax</DependentUpon>
</Compile>
<Compile Include="Models\AccountModels.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\MediaStorage.cs" />
<Compile Include="Services\FeedStorage.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Global.asax" />

View File

@@ -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<SyndicationFeed>();
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<SyndicationFeed>(feed);
var feedPath = HostingEnvironment.MapPath("~/App_Data/Feed.xml");
using (var writer = XmlWriter.Create(feedPath)) {
formatter.WriteTo(writer);
}
}
}
}

View File

@@ -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;
}
}
}