mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-14 10:54:50 +08:00
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:
@@ -42,5 +42,7 @@ namespace Orchard.DevTools.Controllers {
|
||||
model.Results = writer.ToString();
|
||||
return View("Execute", model);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -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" />
|
||||
|
@@ -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();
|
||||
|
@@ -0,0 +1 @@
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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; }
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
}
|
||||
}
|
@@ -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" />
|
||||
<%} %>
|
@@ -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") %> • <%: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>
|
||||
|
@@ -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><%
|
||||
|
@@ -0,0 +1,8 @@
|
||||
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
|
||||
<p>
|
||||
<%:Html.ActionLink("Browse Repository Packages", "Index") %>
|
||||
•
|
||||
<%:Html.ActionLink("Harvest Local Packages", "Harvest") %>
|
||||
•
|
||||
<%:Html.ActionLink("Edit Repository Sources", "Sources") %>
|
||||
</p>
|
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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" />
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user