From 7e9c2388478c67df3d94e8d213945d8ddef53332 Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Sun, 4 Jul 2010 14:14:15 -0700 Subject: [PATCH] Incremental work prototyping packaging Extends Orchard.Modules with Orchard.Modules.Packaging feature IWebSiteFolder capabilities to test FileExists and pull binaries via CopyFileTo Adds rough web-based mechanism for command execution --HG-- branch : dev --- src/Orchard.Tests/Stubs/StubWebSiteFolder.cs | 8 + .../Controllers/CommandsController.cs | 46 +++++ .../Modules/Orchard.DevTools/Module.txt | 5 +- .../Orchard.DevTools/Orchard.DevTools.csproj | 3 + .../ViewModels/CommandsExecuteViewModel.cs | 12 ++ .../Views/Commands/Execute.ascx | 19 ++ .../Modules/Orchard.Modules/Module.txt | 5 +- .../Orchard.Modules/Orchard.Modules.csproj | 8 + .../Packaging/Commands/PackagingCommands.cs | 72 ++++++++ .../Controllers/PackagingController.cs | 46 +++++ .../Packaging/Services/PackageBuilder.cs | 165 ++++++++++++++++++ .../Packaging/Services/PackageRepository.cs | 141 +++++++++++++++ .../ViewModels/PackagingIndexViewModel.cs | 12 ++ .../Views/Packaging/Modules.ascx | 7 + .../Views/Packaging/Sources.ascx | 13 ++ src/Orchard.sln | 7 + src/Orchard/Commands/CommandParameters.cs | 5 +- .../Extensions/Models/FeatureDescriptor.cs | 4 + .../FileSystems/AppData/AppDataFolder.cs | 19 +- .../FileSystems/WebSite/IWebSiteFolder.cs | 4 + .../FileSystems/WebSite/WebSiteFolder.cs | 16 +- 21 files changed, 601 insertions(+), 16 deletions(-) create mode 100644 src/Orchard.Web/Modules/Orchard.DevTools/Controllers/CommandsController.cs create mode 100644 src/Orchard.Web/Modules/Orchard.DevTools/ViewModels/CommandsExecuteViewModel.cs create mode 100644 src/Orchard.Web/Modules/Orchard.DevTools/Views/Commands/Execute.ascx create mode 100644 src/Orchard.Web/Modules/Orchard.Modules/Packaging/Commands/PackagingCommands.cs create mode 100644 src/Orchard.Web/Modules/Orchard.Modules/Packaging/Controllers/PackagingController.cs create mode 100644 src/Orchard.Web/Modules/Orchard.Modules/Packaging/Services/PackageBuilder.cs create mode 100644 src/Orchard.Web/Modules/Orchard.Modules/Packaging/Services/PackageRepository.cs create mode 100644 src/Orchard.Web/Modules/Orchard.Modules/Packaging/ViewModels/PackagingIndexViewModel.cs create mode 100644 src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/Modules.ascx create mode 100644 src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/Sources.ascx diff --git a/src/Orchard.Tests/Stubs/StubWebSiteFolder.cs b/src/Orchard.Tests/Stubs/StubWebSiteFolder.cs index 18a59e1f3..c5004699e 100644 --- a/src/Orchard.Tests/Stubs/StubWebSiteFolder.cs +++ b/src/Orchard.Tests/Stubs/StubWebSiteFolder.cs @@ -14,6 +14,10 @@ namespace Orchard.Tests.Stubs { return Directory.GetDirectories(path); } + public bool FileExists(string virtualPath) { + throw new NotImplementedException(); + } + public string ReadFile(string path) { if (!File.Exists(path)) return null; @@ -21,6 +25,10 @@ namespace Orchard.Tests.Stubs { return File.ReadAllText(path); } + public void CopyFileTo(string virtualPath, Stream destination) { + throw new NotImplementedException(); + } + public IVolatileToken WhenPathChanges(string path) { return new Token {IsCurrent = true}; } diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Controllers/CommandsController.cs b/src/Orchard.Web/Modules/Orchard.DevTools/Controllers/CommandsController.cs new file mode 100644 index 000000000..10fa6a5f7 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Controllers/CommandsController.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using System.Linq; +using System.Web.Mvc; +using Orchard.Commands; +using Orchard.DevTools.ViewModels; +using Orchard.Environment.Extensions; +using Orchard.Themes; +using Orchard.UI.Admin; + +namespace Orchard.DevTools.Controllers { + [Themed, Admin, OrchardFeature("Orchard.DevTools.WebCommandLine")] + public class CommandsController : Controller { + private readonly ICommandManager _commandManager; + + public CommandsController(ICommandManager commandManager) { + _commandManager = commandManager; + } + + public ActionResult Index() { + return Execute(); + } + + public ActionResult Execute() { + return View("Execute", new CommandsExecuteViewModel()); + } + + [HttpPost] + public ActionResult Execute(CommandsExecuteViewModel model) { + + var writer = new StringWriter(); + var parameters = new CommandParameters { + Arguments = model.CommandLine.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries), + Output = writer + }; + + _commandManager.Execute(parameters); + model.History = (model.History ?? Enumerable.Empty()) + .Concat(new[] { model.CommandLine }) + .Distinct() + .ToArray(); + model.Results = writer.ToString(); + return View("Execute", model); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Module.txt b/src/Orchard.Web/Modules/Orchard.DevTools/Module.txt index 1aeb0b2b3..ce617754b 100644 --- a/src/Orchard.Web/Modules/Orchard.DevTools/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Module.txt @@ -16,4 +16,7 @@ features: Profiling: Description: Tools to help profile Orchard. Category: Developer - Dependencies: Orchard.DevTools \ No newline at end of file + Dependencies: Orchard.DevTools + Orchard.DevTools.WebCommandLine: + Description: Enables site administrators to execute Orchard.exe commands via web interface + Category: Developer diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj b/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj index d348d426b..13a415efb 100644 --- a/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj @@ -72,6 +72,7 @@ + @@ -81,6 +82,7 @@ + @@ -92,6 +94,7 @@ + diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/ViewModels/CommandsExecuteViewModel.cs b/src/Orchard.Web/Modules/Orchard.DevTools/ViewModels/CommandsExecuteViewModel.cs new file mode 100644 index 000000000..c1a80a36a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.DevTools/ViewModels/CommandsExecuteViewModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Orchard.DevTools.ViewModels { + public class CommandsExecuteViewModel { + public string[] History { get; set; } + public string CommandLine { get; set; } + public string Results { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Views/Commands/Execute.ascx b/src/Orchard.Web/Modules/Orchard.DevTools/Views/Commands/Execute.ascx new file mode 100644 index 000000000..07ed2b98a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Views/Commands/Execute.ascx @@ -0,0 +1,19 @@ +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> +

+ <%: Html.TitleForPage(T("Command line").ToString()) %>

+
+ <% using (Html.BeginFormAntiForgeryPost(Url.Action("Execute"))) {%> + <%:Html.ValidationSummary()%> +
    + <%for (int index = 0; index != (Model.History ?? new string[0]).Length; ++index) {%>
  • + <%:Model.History[index]%> + <%:Html.HiddenFor(m => m.History[index])%> +
  • + <% + }%>
+ <%:Html.LabelFor(m => m.CommandLine)%> + <%:Html.TextBoxFor(m => m.CommandLine, new { style = "width:100%;" })%> + <%:Html.ValidationMessageFor(m => m.CommandLine)%> +
<%: Model.Results%>
+ <%}%> +
diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Module.txt b/src/Orchard.Web/Modules/Orchard.Modules/Module.txt index c7e8fbcb7..1c958f456 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.Modules/Module.txt @@ -8,4 +8,7 @@ description: The Modules module enables the administrator of the site to manage features: Orchard.Modules: Description: Standard module and feature management. - Category: Core \ No newline at end of file + Category: Core + Orchard.Modules.Packaging: + Description: Standard module and feature management. + Category: Developer diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Orchard.Modules.csproj b/src/Orchard.Web/Modules/Orchard.Modules/Orchard.Modules.csproj index 2668aeaf5..0eb853f18 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Orchard.Modules.csproj +++ b/src/Orchard.Web/Modules/Orchard.Modules/Orchard.Modules.csproj @@ -63,6 +63,7 @@ + @@ -70,6 +71,11 @@ + + + + + @@ -88,6 +94,8 @@ + + diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Packaging/Commands/PackagingCommands.cs b/src/Orchard.Web/Modules/Orchard.Modules/Packaging/Commands/PackagingCommands.cs new file mode 100644 index 000000000..e290cbd7c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Modules/Packaging/Commands/PackagingCommands.cs @@ -0,0 +1,72 @@ +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; + +namespace Orchard.Modules.Packaging.Commands { + [OrchardFeature("Orchard.Modules.Packaging")] + public class PackagingCommands : DefaultOrchardCommandHandler { + private readonly IPackageBuilder _packageBuilder; + + public PackagingCommands(IPackageBuilder packageBuilder) { + _packageBuilder = packageBuilder; + } + + [CommandHelp("harvest \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); + + 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); + } + + } + } + + [CommandHelp("harvest post \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); + + 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); + } + } + catch (WebException webException) { + var text = new StreamReader(webException.Response.GetResponseStream()).ReadToEnd(); + throw new ApplicationException(text); + } + } + } +} + diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Packaging/Controllers/PackagingController.cs b/src/Orchard.Web/Modules/Orchard.Modules/Packaging/Controllers/PackagingController.cs new file mode 100644 index 000000000..634e6297e --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Modules/Packaging/Controllers/PackagingController.cs @@ -0,0 +1,46 @@ +using System; +using System.Web.Mvc; +using Orchard.Environment.Extensions; +using Orchard.Modules.Packaging.Services; +using Orchard.Modules.Packaging.ViewModels; +using Orchard.Themes; +using Orchard.UI.Admin; + +namespace Orchard.Modules.Packaging.Controllers { + [Admin, Themed, OrchardFeature("Orchard.Modules.Packaging")] + public class PackagingController : Controller { + private readonly IPackageRepository _packageRepository; + + public PackagingController(IPackageRepository packageRepository) { + _packageRepository = packageRepository; + } + + 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(), + }); + } + + public ActionResult AddSource(string url) { + _packageRepository.AddSource(new PackageSource { Id = Guid.NewGuid(), FeedUrl = url }); + return RedirectToAction("Index"); + } + + public ActionResult Update() { + _packageRepository.UpdateLists(); + //notifier + return RedirectToAction("Index"); + } + + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Packaging/Services/PackageBuilder.cs b/src/Orchard.Web/Modules/Orchard.Modules/Packaging/Services/PackageBuilder.cs new file mode 100644 index 000000000..9579581b8 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Modules/Packaging/Services/PackageBuilder.cs @@ -0,0 +1,165 @@ +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.WebSite; + +namespace Orchard.Modules.Packaging.Services { + public interface IPackageBuilder : IDependency { + Stream Create(string moduleName); + } + + [OrchardFeature("Orchard.Modules.Packaging")] + public class PackageBuilder : IPackageBuilder { + private readonly IExtensionManager _extensionManager; + private readonly IWebSiteFolder _webSiteFolder; + + public PackageBuilder(IExtensionManager extensionManager, IWebSiteFolder webSiteFolder) { + _extensionManager = extensionManager; + _webSiteFolder = webSiteFolder; + } + + class CreateContext { + public Stream Stream { get; set; } + public Package Package { get; set; } + + public IWebSiteFolder SourceFolder { get; set; } + public string SourcePath { get; set; } + public string TargetPath { get; set; } + + 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"; + + var context = new CreateContext(); + BeginPackage(context); + 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); + } + EndPackage(context); + + return context.Stream; + } + + private void SetCoreProperties(CreateContext context, ExtensionDescriptor extensionDescriptor) { + var properties = context.Package.PackageProperties; + properties.Title = extensionDescriptor.DisplayName ?? extensionDescriptor.Name; + //properties.Subject = ""; + properties.Creator = extensionDescriptor.Author; + properties.Keywords = extensionDescriptor.Tags; + properties.Description = extensionDescriptor.Description; + //properties.LastModifiedBy = ""; + //properties.Revision = ""; + //properties.LastPrinted = ""; + //properties.Created = ""; + //properties.Modified = ""; + properties.Category = extensionDescriptor.Features.Where(f => f.Name == extensionDescriptor.Name).Select(f => f.Category).FirstOrDefault(); + properties.Identifier = extensionDescriptor.Name; + properties.ContentType = "Orchard " + extensionDescriptor.ExtensionType; + //properties.Language = ""; + properties.Version = extensionDescriptor.Version; + properties.ContentStatus = ""; + } + + + private void EmbedProjectFiles(CreateContext 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) { + EmbedVirtualFile(context, includePath, System.Net.Mime.MediaTypeNames.Application.Octet); + } + } + } + + private void EmbedReferenceFiles(CreateContext 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 (context.SourceFolder.FileExists(context.SourcePath + virtualPath)) { + EmbedVirtualFile(context, virtualPath, System.Net.Mime.MediaTypeNames.Application.Octet); + } + else if (hintPath != null) { + } + } + } + + private XName Ns(string localName) { + return XName.Get(localName, "http://schemas.microsoft.com/developer/msbuild/2003"); + } + + + static void BeginPackage(CreateContext context) { + context.Stream = new MemoryStream(); + context.Package = Package.Open(context.Stream, FileMode.Create, FileAccess.ReadWrite); + } + + static void EstablishPaths(CreateContext context, IWebSiteFolder webSiteFolder, string locationPath, string moduleName) { + context.SourceFolder = webSiteFolder; + context.SourcePath = "~/Modules/" + moduleName + "/"; + context.TargetPath = "\\" + moduleName + "\\"; + } + + static bool LoadProject(CreateContext context, string relativePath) { + var virtualPath = context.SourcePath + relativePath; + if (context.SourceFolder.FileExists(virtualPath)) { + context.Project = XDocument.Parse(context.SourceFolder.ReadFile(context.SourcePath + relativePath)); + return true; + } + return false; + } + + static Uri EmbedVirtualFile(CreateContext context, string relativePath, string contentType) { + var partUri = PackUriHelper.CreatePartUri(new Uri(context.TargetPath + relativePath, UriKind.Relative)); + var packagePart = context.Package.CreatePart(partUri, contentType); + using (var stream = packagePart.GetStream(FileMode.Create, FileAccess.Write)) { + context.SourceFolder.CopyFileTo(context.SourcePath + relativePath, stream); + } + return partUri; + } + + static void EndPackage(CreateContext context) { + context.Package.Close(); + } + + + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Packaging/Services/PackageRepository.cs b/src/Orchard.Web/Modules/Orchard.Modules/Packaging/Services/PackageRepository.cs new file mode 100644 index 000000000..d18f0213c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Modules/Packaging/Services/PackageRepository.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Web; +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 { + IEnumerable GetSources(); + void AddSource(PackageSource source); + void RemoveSource(Guid id); + void UpdateLists(); + + IEnumerable GetModuleList(); + } + + public class PackageSource { + public Guid Id { get; set; } + public string FeedUrl { get; set; } + } + + public class PackageInfo { + public PackageSource Source { get; set; } + + public AtomEntry AtomEntry { 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) { + var element = entry.Element(AtomXName(localName)); + return element != null ? element.Value : null; + } + + public static XName AtomXName(string localName) { + return XName.Get(localName, "http://www.w3.org/2005/Atom"); + } + } + + [OrchardFeature("Orchard.Modules.Packaging")] + public class PackageRepository : IPackageRepository { + private readonly IAppDataFolder _appDataFolder; + private static readonly XmlSerializer _sourceSerializer = new XmlSerializer(typeof(List), new XmlRootAttribute("Sources")); + + public PackageRepository(IAppDataFolder appDataFolder) { + _appDataFolder = appDataFolder; + } + + static string GetSourcesPath() { + return ".Packaging/Sources.xml"; + } + static string GetFeedCachePath(PackageSource source) { + return ".Packaging/Feed." + source.Id.ToString("n") + ".xml"; + } + + public IEnumerable GetSources() { + var text = _appDataFolder.ReadFile(GetSourcesPath()); + if (string.IsNullOrEmpty(text)) + return Enumerable.Empty(); + + var textReader = new StringReader(_appDataFolder.ReadFile(GetSourcesPath())); + return (IEnumerable)_sourceSerializer.Deserialize(textReader); + } + + void SaveSources(IEnumerable sources) { + var textWriter = new StringWriter(); + _sourceSerializer.Serialize(textWriter, sources.ToList()); + + _appDataFolder.CreateFile(GetSourcesPath(), textWriter.ToString()); + } + + public void AddSource(PackageSource source) { + UpdateSource(source); + SaveSources(GetSources().Concat(new[] { source })); + } + + public void RemoveSource(Guid id) { + SaveSources(GetSources().Where(x => x.Id != id)); + } + + public void UpdateLists() { + foreach (var source in GetSources()) { + UpdateSource(source); + } + } + + private void UpdateSource(PackageSource source) { + var feed = XDocument.Load(source.FeedUrl, LoadOptions.PreserveWhitespace); + _appDataFolder.CreateFile(GetFeedCachePath(source), feed.ToString(SaveOptions.DisableFormatting)); + } + + + static XName Atom(string localName) { + return AtomExtensions.AtomXName(localName); + } + + public IEnumerable 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 Unit(T t) where T : class { + return t != null ? new[] { t } : Enumerable.Empty(); + } + static IEnumerable Bind(T t, Func> f) where T : class { + return Unit(t).SelectMany(f); + } + } + + +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Packaging/ViewModels/PackagingIndexViewModel.cs b/src/Orchard.Web/Modules/Orchard.Modules/Packaging/ViewModels/PackagingIndexViewModel.cs new file mode 100644 index 000000000..9d719fe9a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Modules/Packaging/ViewModels/PackagingIndexViewModel.cs @@ -0,0 +1,12 @@ +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 Sources { get; set; } + public IEnumerable Modules { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/Modules.ascx b/src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/Modules.ascx new file mode 100644 index 000000000..e52aaf4f8 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/Modules.ascx @@ -0,0 +1,7 @@ +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> +<%@ 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 %>
  • <% + }%>
+ diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/Sources.ascx b/src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/Sources.ascx new file mode 100644 index 000000000..f5cf543f9 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Modules/Views/Packaging/Sources.ascx @@ -0,0 +1,13 @@ +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> +<%@ Import Namespace="Orchard.Mvc.Html" %> +

+ <%: Html.TitleForPage(T("Packages").ToString()) %>

+
    + <%foreach (var item in Model.Sources) {%>
  • + <%:Html.Link(item.FeedUrl, item.FeedUrl)%>
  • <% + }%>
+<%using (Html.BeginFormAntiForgeryPost(Url.Action("AddSource"))) {%> +Url: +<%:Html.TextBox("url") %> + +<%} %> diff --git a/src/Orchard.sln b/src/Orchard.sln index e18c0dcd4..180aacc4c 100644 --- a/src/Orchard.sln +++ b/src/Orchard.sln @@ -71,6 +71,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Indexing", "Orchard EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.ContentTypes", "Orchard.Web\Modules\Orchard.ContentTypes\Orchard.ContentTypes.csproj", "{0E7646E8-FE8F-43C1-8799-D97860925EC4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageIndexReferenceImplementation", "Tools\PackageIndexReferenceImplementation\PackageIndexReferenceImplementation.csproj", "{8A4E42CE-79F8-4BE2-8B1E-A6B83432123B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -201,6 +203,10 @@ Global {0E7646E8-FE8F-43C1-8799-D97860925EC4}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E7646E8-FE8F-43C1-8799-D97860925EC4}.Release|Any CPU.ActiveCfg = Release|Any CPU {0E7646E8-FE8F-43C1-8799-D97860925EC4}.Release|Any CPU.Build.0 = Release|Any CPU + {8A4E42CE-79F8-4BE2-8B1E-A6B83432123B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A4E42CE-79F8-4BE2-8B1E-A6B83432123B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A4E42CE-79F8-4BE2-8B1E-A6B83432123B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A4E42CE-79F8-4BE2-8B1E-A6B83432123B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -233,6 +239,7 @@ Global {5E5E7A21-C7B2-44D8-8593-2F9541AE041D} = {383DBA32-4A3E-48D1-AAC3-75377A694452} {4AB4B5B6-277E-4FF6-B69B-7AE9E16D2A56} = {383DBA32-4A3E-48D1-AAC3-75377A694452} {33B1BC8D-E292-4972-A363-22056B207156} = {383DBA32-4A3E-48D1-AAC3-75377A694452} + {8A4E42CE-79F8-4BE2-8B1E-A6B83432123B} = {383DBA32-4A3E-48D1-AAC3-75377A694452} {E65E5633-C0FF-453C-A906-481C14F969D6} = {E75A4CE4-CAA6-41E4-B951-33ACC60DC77C} EndGlobalSection EndGlobal diff --git a/src/Orchard/Commands/CommandParameters.cs b/src/Orchard/Commands/CommandParameters.cs index 6851549ab..c11409149 100644 --- a/src/Orchard/Commands/CommandParameters.cs +++ b/src/Orchard/Commands/CommandParameters.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; namespace Orchard.Commands { public class CommandParameters { diff --git a/src/Orchard/Environment/Extensions/Models/FeatureDescriptor.cs b/src/Orchard/Environment/Extensions/Models/FeatureDescriptor.cs index 525601add..1aea1d2ba 100644 --- a/src/Orchard/Environment/Extensions/Models/FeatureDescriptor.cs +++ b/src/Orchard/Environment/Extensions/Models/FeatureDescriptor.cs @@ -1,5 +1,9 @@ namespace Orchard.Environment.Extensions.Models { public class FeatureDescriptor { + public FeatureDescriptor() { + Dependencies = new string[0]; + } + public ExtensionDescriptor Extension { get; set; } public string Name { get; set; } diff --git a/src/Orchard/FileSystems/AppData/AppDataFolder.cs b/src/Orchard/FileSystems/AppData/AppDataFolder.cs index f6cb73610..c1e408144 100644 --- a/src/Orchard/FileSystems/AppData/AppDataFolder.cs +++ b/src/Orchard/FileSystems/AppData/AppDataFolder.cs @@ -93,8 +93,8 @@ namespace Orchard.FileSystems.AppData { } public void CreateFile(string path, string content) { - using(var stream = CreateFile(path)) { - using(var tw = new StreamWriter(stream)) { + using (var stream = CreateFile(path)) { + using (var tw = new StreamWriter(stream)) { tw.Write(content); } } @@ -109,7 +109,8 @@ namespace Orchard.FileSystems.AppData { } public string ReadFile(string path) { - return File.ReadAllText(CombineToPhysicalPath(path)); + var physicalPath = CombineToPhysicalPath(path); + return File.Exists(physicalPath) ? File.ReadAllText(physicalPath) : null; } public Stream OpenFile(string path) { @@ -145,9 +146,9 @@ namespace Orchard.FileSystems.AppData { var files = Directory.GetFiles(directoryPath); return files.Select(file => { - var fileName = Path.GetFileName(file); - return Combine(path, fileName); - }); + var fileName = Path.GetFileName(file); + return Combine(path, fileName); + }); } public IEnumerable ListDirectories(string path) { @@ -158,9 +159,9 @@ namespace Orchard.FileSystems.AppData { var files = Directory.GetDirectories(directoryPath); return files.Select(file => { - var fileName = Path.GetFileName(file); - return Combine(path, fileName); - }); + var fileName = Path.GetFileName(file); + return Combine(path, fileName); + }); } public void CreateDirectory(string path) { diff --git a/src/Orchard/FileSystems/WebSite/IWebSiteFolder.cs b/src/Orchard/FileSystems/WebSite/IWebSiteFolder.cs index 09d6176c1..45f621ede 100644 --- a/src/Orchard/FileSystems/WebSite/IWebSiteFolder.cs +++ b/src/Orchard/FileSystems/WebSite/IWebSiteFolder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using Orchard.Caching; namespace Orchard.FileSystems.WebSite { @@ -8,7 +9,10 @@ namespace Orchard.FileSystems.WebSite { /// public interface IWebSiteFolder : IVolatileProvider { IEnumerable ListDirectories(string virtualPath); + + bool FileExists(string virtualPath); string ReadFile(string virtualPath); + void CopyFileTo(string virtualPath, Stream destination); IVolatileToken WhenPathChanges(string virtualPath); void WhenPathChanges(string virtualPath, Action action); diff --git a/src/Orchard/FileSystems/WebSite/WebSiteFolder.cs b/src/Orchard/FileSystems/WebSite/WebSiteFolder.cs index 653085840..732ef2e21 100644 --- a/src/Orchard/FileSystems/WebSite/WebSiteFolder.cs +++ b/src/Orchard/FileSystems/WebSite/WebSiteFolder.cs @@ -25,18 +25,28 @@ namespace Orchard.FileSystems.WebSite { .Select(d => d.VirtualPath) .ToArray(); } + + public bool FileExists(string virtualPath) { + return HostingEnvironment.VirtualPathProvider.FileExists(virtualPath); + } public string ReadFile(string virtualPath) { if (!HostingEnvironment.VirtualPathProvider.FileExists(virtualPath)) return null; - using (var stream = VirtualPathProvider.OpenFile(virtualPath)) { + using (var stream = VirtualPathProvider.OpenFile(Normalize(virtualPath))) { using (var reader = new StreamReader(stream)) { return reader.ReadToEnd(); } } } + public void CopyFileTo(string virtualPath, Stream destination) { + using (var stream = VirtualPathProvider.OpenFile(Normalize(virtualPath))) { + stream.CopyTo(destination); + } + } + public IVolatileToken WhenPathChanges(string virtualPath) { return _virtualPathMonitor.WhenPathChanges(virtualPath); } @@ -44,5 +54,9 @@ namespace Orchard.FileSystems.WebSite { public void WhenPathChanges(string virtualPath, Action action) { _virtualPathMonitor.WhenPathChanges(virtualPath, action); } + + static string Normalize(string virtualPath) { + return HostingEnvironment.VirtualPathProvider.GetFile(virtualPath).VirtualPath; + } } } \ No newline at end of file