mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Merge
--HG-- branch : dev
This commit is contained in:
62
src/Orchard.Azure.Tests/AzureVirtualEnvironmentTest.cs
Normal file
62
src/Orchard.Azure.Tests/AzureVirtualEnvironmentTest.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Configuration;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Microsoft.WindowsAzure.StorageClient;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Orchard.Azure.Tests {
|
||||
public abstract class AzureVirtualEnvironmentTest {
|
||||
private Process _dsService;
|
||||
|
||||
protected abstract void OnInit();
|
||||
|
||||
[TestFixtureSetUp]
|
||||
public void FixtureSetup() {
|
||||
var count = Process.GetProcessesByName("DSService").Length;
|
||||
if ( count == 0 ) {
|
||||
var start = new ProcessStartInfo {
|
||||
Arguments = "/devstore:start",
|
||||
FileName =
|
||||
Path.Combine(ConfigurationManager.AppSettings["AzureSDK"], @"bin\csrun.exe")
|
||||
};
|
||||
|
||||
_dsService = new Process { StartInfo = start };
|
||||
_dsService.Start();
|
||||
_dsService.WaitForExit();
|
||||
}
|
||||
|
||||
OnInit();
|
||||
}
|
||||
|
||||
[TestFixtureTearDown]
|
||||
public void FixtureTearDown() {
|
||||
|
||||
if ( _dsService != null )
|
||||
_dsService.Close();
|
||||
}
|
||||
|
||||
protected void DeleteAllBlobs(CloudBlobContainer container) {
|
||||
foreach ( var blob in container.ListBlobs() ) {
|
||||
if ( blob is CloudBlob ) {
|
||||
( (CloudBlob)blob ).DeleteIfExists();
|
||||
}
|
||||
|
||||
if ( blob is CloudBlobDirectory ) {
|
||||
DeleteAllBlobs((CloudBlobDirectory)blob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteAllBlobs(CloudBlobDirectory cloudBlobDirectory) {
|
||||
foreach ( var blob in cloudBlobDirectory.ListBlobs() ) {
|
||||
if ( blob is CloudBlob ) {
|
||||
( (CloudBlob)blob ).DeleteIfExists();
|
||||
}
|
||||
|
||||
if ( blob is CloudBlobDirectory ) {
|
||||
DeleteAllBlobs((CloudBlobDirectory)blob);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.WindowsAzure;
|
||||
using NUnit.Framework;
|
||||
using Orchard.Azure.Environment.Configuration;
|
||||
using Orchard.Environment.Configuration;
|
||||
|
||||
namespace Orchard.Azure.Tests.Environment.Configuration {
|
||||
[TestFixture]
|
||||
public class AzureShellSettingsManagerTests : AzureVirtualEnvironmentTest {
|
||||
|
||||
protected IShellSettingsManager Loader;
|
||||
|
||||
protected override void OnInit() {
|
||||
CloudStorageAccount devAccount;
|
||||
CloudStorageAccount.TryParse("UseDevelopmentStorage=true", out devAccount);
|
||||
|
||||
Loader = new AzureShellSettingsManager(devAccount);
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void Setup() {
|
||||
// ensure default container is empty before running any test
|
||||
DeleteAllBlobs( ((AzureShellSettingsManager)Loader).Container);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SingleSettingsFileShouldComeBackAsExpected() {
|
||||
|
||||
Loader.SaveSettings(new ShellSettings { Name = "Default", DataProvider = "SQLite", DataConnectionString = "something else" });
|
||||
|
||||
var settings = Loader.LoadSettings().Single();
|
||||
Assert.That(settings, Is.Not.Null);
|
||||
Assert.That(settings.Name, Is.EqualTo("Default"));
|
||||
Assert.That(settings.DataProvider, Is.EqualTo("SQLite"));
|
||||
Assert.That(settings.DataConnectionString, Is.EqualTo("something else"));
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void MultipleFilesCanBeDetected() {
|
||||
|
||||
Loader.SaveSettings(new ShellSettings { Name = "Default", DataProvider = "SQLite", DataConnectionString = "something else" });
|
||||
Loader.SaveSettings(new ShellSettings { Name = "Another", DataProvider = "SQLite2", DataConnectionString = "something else2" });
|
||||
|
||||
var settings = Loader.LoadSettings();
|
||||
Assert.That(settings.Count(), Is.EqualTo(2));
|
||||
|
||||
var def = settings.Single(x => x.Name == "Default");
|
||||
Assert.That(def.Name, Is.EqualTo("Default"));
|
||||
Assert.That(def.DataProvider, Is.EqualTo("SQLite"));
|
||||
Assert.That(def.DataConnectionString, Is.EqualTo("something else"));
|
||||
|
||||
var alt = settings.Single(x => x.Name == "Another");
|
||||
Assert.That(alt.Name, Is.EqualTo("Another"));
|
||||
Assert.That(alt.DataProvider, Is.EqualTo("SQLite2"));
|
||||
Assert.That(alt.DataConnectionString, Is.EqualTo("something else2"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NewSettingsCanBeStored() {
|
||||
Loader.SaveSettings(new ShellSettings { Name = "Default", DataProvider = "SQLite", DataConnectionString = "something else" });
|
||||
|
||||
var foo = new ShellSettings { Name = "Foo", DataProvider = "Bar", DataConnectionString = "Quux" };
|
||||
|
||||
Assert.That(Loader.LoadSettings().Count(), Is.EqualTo(1));
|
||||
Loader.SaveSettings(foo);
|
||||
Assert.That(Loader.LoadSettings().Count(), Is.EqualTo(2));
|
||||
|
||||
var text = ( (AzureShellSettingsManager)Loader ).Container.GetBlockBlobReference("Foo/Settings.txt").DownloadText();
|
||||
Assert.That(text, Is.StringContaining("Foo"));
|
||||
Assert.That(text, Is.StringContaining("Bar"));
|
||||
Assert.That(text, Is.StringContaining("Quux"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -53,6 +53,8 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AzureVirtualEnvironmentTest.cs" />
|
||||
<Compile Include="Environment\Configuration\AzureShellSettingsManagerTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Storage\AzureBlobStorageProviderTests.cs" />
|
||||
</ItemGroup>
|
||||
|
@@ -11,74 +11,21 @@ using System.Text;
|
||||
|
||||
namespace Orchard.Azure.Tests.Storage {
|
||||
[TestFixture]
|
||||
public class AzureBlobStorageProviderTests {
|
||||
public class AzureBlobStorageProviderTests : AzureVirtualEnvironmentTest {
|
||||
|
||||
#region Azure Environment initialization
|
||||
|
||||
private Process _dsService;
|
||||
|
||||
[TestFixtureSetUp]
|
||||
public void FixtureSetup() {
|
||||
var count = Process.GetProcessesByName("DSService").Length;
|
||||
if (count == 0)
|
||||
{
|
||||
var start = new ProcessStartInfo
|
||||
{
|
||||
Arguments = "/devstore:start",
|
||||
FileName =
|
||||
Path.Combine(ConfigurationManager.AppSettings["AzureSDK"], @"bin\csrun.exe")
|
||||
};
|
||||
|
||||
_dsService = new Process { StartInfo = start };
|
||||
_dsService.Start();
|
||||
_dsService.WaitForExit();
|
||||
}
|
||||
private AzureBlobStorageProvider _azureBlobStorageProvider;
|
||||
|
||||
protected override void OnInit() {
|
||||
CloudStorageAccount devAccount;
|
||||
CloudStorageAccount.TryParse("UseDevelopmentStorage=true", out devAccount);
|
||||
|
||||
_azureBlobStorageProvider = new AzureBlobStorageProvider("default", devAccount);
|
||||
}
|
||||
|
||||
[TestFixtureTearDown]
|
||||
public void FixtureTearDown() {
|
||||
|
||||
if(_dsService != null)
|
||||
_dsService.Close();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void Setup() {
|
||||
// ensure default container is empty before running any test
|
||||
DeleteAllBlobs();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private AzureBlobStorageProvider _azureBlobStorageProvider;
|
||||
|
||||
private void DeleteAllBlobs() {
|
||||
foreach(var blob in _azureBlobStorageProvider.Container.ListBlobs()) {
|
||||
if(blob is CloudBlob) {
|
||||
((CloudBlob) blob).DeleteIfExists();
|
||||
}
|
||||
|
||||
if (blob is CloudBlobDirectory) {
|
||||
DeleteAllBlobs((CloudBlobDirectory)blob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteAllBlobs(CloudBlobDirectory cloudBlobDirectory) {
|
||||
foreach (var blob in cloudBlobDirectory.ListBlobs()) {
|
||||
if (blob is CloudBlob) {
|
||||
((CloudBlob)blob).DeleteIfExists();
|
||||
}
|
||||
|
||||
if (blob is CloudBlobDirectory) {
|
||||
DeleteAllBlobs((CloudBlobDirectory)blob);
|
||||
}
|
||||
}
|
||||
DeleteAllBlobs(_azureBlobStorageProvider.Container);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -131,8 +78,7 @@ namespace Orchard.Azure.Tests.Storage {
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateFileShouldBeFolderAgnostic()
|
||||
{
|
||||
public void CreateFileShouldBeFolderAgnostic() {
|
||||
_azureBlobStorageProvider.CreateFile("foo.txt");
|
||||
_azureBlobStorageProvider.CreateFile("folder/foo.txt");
|
||||
_azureBlobStorageProvider.CreateFile("folder/folder/foo.txt");
|
||||
@@ -193,9 +139,9 @@ namespace Orchard.Azure.Tests.Storage {
|
||||
|
||||
var foo = _azureBlobStorageProvider.CreateFile("folder1/foo.txt");
|
||||
|
||||
using(var stream = foo.OpenWrite())
|
||||
using (var writer = new StreamWriter(stream))
|
||||
writer.Write(teststring);
|
||||
using ( var stream = foo.OpenWrite() )
|
||||
using ( var writer = new StreamWriter(stream) )
|
||||
writer.Write(teststring);
|
||||
|
||||
Assert.AreEqual(22, foo.GetSize());
|
||||
|
||||
|
58
src/Orchard.Azure/CloudBlobContainerExtensions.cs
Normal file
58
src/Orchard.Azure/CloudBlobContainerExtensions.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Microsoft.WindowsAzure.StorageClient;
|
||||
|
||||
namespace Orchard.Azure {
|
||||
public static class CloudBlobContainerExtensions {
|
||||
|
||||
public static bool BlobExists(this CloudBlobContainer container, string path) {
|
||||
if ( String.IsNullOrEmpty(path) || path.Trim() == String.Empty )
|
||||
throw new ArgumentException("Path can't be empty");
|
||||
|
||||
try {
|
||||
var blob = container.GetBlockBlobReference(path);
|
||||
blob.FetchAttributes();
|
||||
return true;
|
||||
}
|
||||
catch ( StorageClientException e ) {
|
||||
if ( e.ErrorCode == StorageErrorCode.ResourceNotFound ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static void EnsureBlobExists(this CloudBlobContainer container, string path) {
|
||||
if ( !BlobExists(container, path) ) {
|
||||
throw new ArgumentException("File " + path + " does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
public static void EnsureBlobDoesNotExist(this CloudBlobContainer container, string path) {
|
||||
if ( BlobExists(container, path) ) {
|
||||
throw new ArgumentException("File " + path + " already exists");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool DirectoryExists(this CloudBlobContainer container, string path) {
|
||||
if ( String.IsNullOrEmpty(path) || path.Trim() == String.Empty )
|
||||
throw new ArgumentException("Path can't be empty");
|
||||
|
||||
return container.GetDirectoryReference(path).ListBlobs().Count() > 0;
|
||||
}
|
||||
|
||||
public static void EnsureDirectoryExists(this CloudBlobContainer container, string path) {
|
||||
if ( !DirectoryExists(container, path) ) {
|
||||
throw new ArgumentException("Directory " + path + " does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
public static void EnsureDirectoryDoesNotExist(this CloudBlobContainer container, string path) {
|
||||
if ( DirectoryExists(container, path) ) {
|
||||
throw new ArgumentException("Directory " + path + " already exists");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Yaml.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.WindowsAzure.StorageClient;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Microsoft.WindowsAzure;
|
||||
|
||||
namespace Orchard.Azure.Environment.Configuration {
|
||||
|
||||
public class AzureShellSettingsManager : IShellSettingsManager {
|
||||
public const string ContainerName = "sites"; // container names must be lower cased
|
||||
|
||||
private readonly CloudStorageAccount _storageAccount;
|
||||
public CloudBlobClient BlobClient { get; private set; }
|
||||
public CloudBlobContainer Container { get; private set; }
|
||||
|
||||
Localizer T { get; [UsedImplicitly]
|
||||
set; }
|
||||
|
||||
public AzureShellSettingsManager() : this(CloudStorageAccount.FromConfigurationSetting("DataConnectionString"))
|
||||
{
|
||||
}
|
||||
|
||||
public AzureShellSettingsManager(CloudStorageAccount storageAccount)
|
||||
{
|
||||
// Setup the connection to custom storage accountm, e.g. Development Storage
|
||||
_storageAccount = storageAccount;
|
||||
|
||||
BlobClient = _storageAccount.CreateCloudBlobClient();
|
||||
|
||||
// Get and create the container if it does not exist
|
||||
// The container is named with DNS naming restrictions (i.e. all lower case)
|
||||
Container = new CloudBlobContainer(ContainerName, BlobClient);
|
||||
Container.CreateIfNotExist();
|
||||
|
||||
// Tenant settings are protected by default
|
||||
Container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Off });
|
||||
}
|
||||
|
||||
IEnumerable<ShellSettings> IShellSettingsManager.LoadSettings() {
|
||||
return LoadSettings().ToArray();
|
||||
}
|
||||
|
||||
void IShellSettingsManager.SaveSettings(ShellSettings settings) {
|
||||
if ( settings == null )
|
||||
throw new ArgumentException(T("There are no settings to save.").ToString());
|
||||
if ( string.IsNullOrEmpty(settings.Name) )
|
||||
throw new ArgumentException(T("Settings \"Name\" is not set.").ToString());
|
||||
|
||||
var filePath =String.Concat(settings.Name, "/", "Settings.txt");
|
||||
var blob = Container.GetBlockBlobReference(filePath);
|
||||
blob.UploadText(ComposeSettings(settings));
|
||||
}
|
||||
|
||||
IEnumerable<ShellSettings> LoadSettings() {
|
||||
var settingsBlobs =
|
||||
BlobClient.ListBlobsWithPrefix(Container.Name + "/" ).OfType<CloudBlobDirectory>()
|
||||
.SelectMany(directory => directory.ListBlobs()).OfType<CloudBlockBlob>()
|
||||
.Where(blob => string.Equals(Path.GetFileName(blob.Uri.ToString()), "Settings.txt", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
foreach ( var settingsBlob in settingsBlobs ) {
|
||||
yield return ParseSettings(settingsBlob.DownloadText());
|
||||
}
|
||||
}
|
||||
|
||||
class Content {
|
||||
public string Name { get; set; }
|
||||
public string DataProvider { get; set; }
|
||||
public string DataConnectionString { get; set; }
|
||||
public string DataPrefix { get; set; }
|
||||
public string RequestUrlHost { get; set; }
|
||||
public string RequestUrlPrefix { get; set; }
|
||||
public string State { get; set; }
|
||||
}
|
||||
|
||||
static ShellSettings ParseSettings(string text) {
|
||||
var ser = new YamlSerializer();
|
||||
var content = ser.Deserialize(text, typeof(Content)).Cast<Content>().Single();
|
||||
return new ShellSettings {
|
||||
Name = content.Name,
|
||||
DataProvider = content.DataProvider,
|
||||
DataConnectionString = content.DataConnectionString,
|
||||
DataTablePrefix = content.DataPrefix,
|
||||
RequestUrlHost = content.RequestUrlHost,
|
||||
RequestUrlPrefix = content.RequestUrlPrefix,
|
||||
State = new TenantState(content.State)
|
||||
};
|
||||
}
|
||||
|
||||
static string ComposeSettings(ShellSettings settings) {
|
||||
if ( settings == null )
|
||||
return "";
|
||||
|
||||
var ser = new YamlSerializer();
|
||||
return ser.Serialize(new Content {
|
||||
Name = settings.Name,
|
||||
DataProvider = settings.DataProvider,
|
||||
DataConnectionString = settings.DataConnectionString,
|
||||
DataPrefix = settings.DataTablePrefix,
|
||||
RequestUrlHost = settings.RequestUrlHost,
|
||||
RequestUrlPrefix = settings.RequestUrlPrefix,
|
||||
State = settings.State != null ? settings.State.ToString() : String.Empty
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -44,8 +44,14 @@
|
||||
</Reference>
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="YamlSerializer, Version=0.9.0.2, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\lib\yaml\YamlSerializer.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CloudBlobContainerExtensions.cs" />
|
||||
<Compile Include="Environment\Configuration\AzureShellSettingsManager.cs" />
|
||||
<Compile Include="Storage\AzureBlobStorageProvider.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
@@ -6,125 +6,68 @@ using Microsoft.WindowsAzure.StorageClient;
|
||||
using System.IO;
|
||||
using Orchard.Storage;
|
||||
|
||||
namespace Orchard.Azure.Storage
|
||||
{
|
||||
public interface IDependency {}
|
||||
namespace Orchard.Azure.Storage {
|
||||
public interface IDependency { }
|
||||
|
||||
public class AzureBlobStorageProvider : IStorageProvider {
|
||||
public const string ContainerName = "media"; // container names must be lower cased
|
||||
|
||||
public class AzureBlobStorageProvider : IStorageProvider
|
||||
{
|
||||
private readonly CloudStorageAccount _storageAccount;
|
||||
private string _shellName;
|
||||
public CloudBlobClient BlobClient { get; private set; }
|
||||
public CloudBlobContainer Container { get; private set; }
|
||||
|
||||
public AzureBlobStorageProvider(string containerName) : this(containerName, CloudStorageAccount.FromConfigurationSetting("DataConnectionString"))
|
||||
{
|
||||
public AzureBlobStorageProvider(string shellName)
|
||||
: this(shellName, CloudStorageAccount.FromConfigurationSetting("DataConnectionString")) {
|
||||
}
|
||||
|
||||
public AzureBlobStorageProvider(string containerName, CloudStorageAccount storageAccount)
|
||||
{
|
||||
public AzureBlobStorageProvider(string shellName, CloudStorageAccount storageAccount) {
|
||||
// Setup the connection to custom storage accountm, e.g. Development Storage
|
||||
_storageAccount = storageAccount;
|
||||
|
||||
_shellName = shellName;
|
||||
|
||||
BlobClient = _storageAccount.CreateCloudBlobClient();
|
||||
// Get and create the container if it does not exist
|
||||
// The container is named with DNS naming restrictions (i.e. all lower case)
|
||||
Container = BlobClient.GetContainerReference(containerName);
|
||||
Container = BlobClient.GetContainerReference(ContainerName);
|
||||
Container.CreateIfNotExist();
|
||||
|
||||
Container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Container });
|
||||
}
|
||||
|
||||
private static void EnsurePathIsRelative(string path) {
|
||||
if(path.StartsWith("/"))
|
||||
if ( path.StartsWith("/") || path.StartsWith("http://"))
|
||||
throw new ArgumentException("Path must be relative");
|
||||
}
|
||||
|
||||
private string GetPrefix(string path) {
|
||||
var prefix = String.Concat(Container.Name, "/", path);
|
||||
if (prefix.EndsWith("/"))
|
||||
return prefix;
|
||||
|
||||
return String.Concat(prefix, "/");
|
||||
}
|
||||
|
||||
private bool BlobExists(string path) {
|
||||
if(String.IsNullOrEmpty(path) || path.Trim() == String.Empty)
|
||||
throw new ArgumentException("Path can't be empty");
|
||||
|
||||
try {
|
||||
var blob = Container.GetBlockBlobReference(path);
|
||||
blob.FetchAttributes();
|
||||
return true;
|
||||
}
|
||||
catch (StorageClientException e) {
|
||||
if (e.ErrorCode == StorageErrorCode.ResourceNotFound) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureBlobExists(string path)
|
||||
{
|
||||
if (!BlobExists(path)) {
|
||||
throw new ArgumentException("File " + path + " does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureBlobDoesNotExist(string path)
|
||||
{
|
||||
if (BlobExists(path)) {
|
||||
throw new ArgumentException("File " + path + " already exists");
|
||||
}
|
||||
}
|
||||
|
||||
private bool DirectoryExists(string path)
|
||||
{
|
||||
if (String.IsNullOrEmpty(path) || path.Trim() == String.Empty)
|
||||
throw new ArgumentException("Path can't be empty");
|
||||
|
||||
return Container.GetDirectoryReference(path).ListBlobs().Count() > 0;
|
||||
}
|
||||
|
||||
private void EnsureDirectoryExists(string path)
|
||||
{
|
||||
if (!DirectoryExists(path)) {
|
||||
throw new ArgumentException("Directory " + path + " does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureDirectoryDoesNotExist(string path)
|
||||
{
|
||||
if (DirectoryExists(path)) {
|
||||
throw new ArgumentException("Directory " + path + " already exists");
|
||||
}
|
||||
}
|
||||
|
||||
#region IStorageProvider Members
|
||||
|
||||
public IStorageFile GetFile(string path) {
|
||||
EnsurePathIsRelative(path);
|
||||
EnsureBlobExists(path);
|
||||
Container.EnsureBlobExists(path);
|
||||
return new AzureBlobFileStorage(Container.GetBlockBlobReference(path));
|
||||
}
|
||||
|
||||
public IEnumerable<IStorageFile> ListFiles(string path)
|
||||
{
|
||||
public IEnumerable<IStorageFile> ListFiles(string path) {
|
||||
EnsurePathIsRelative(path);
|
||||
foreach (var blobItem in BlobClient.ListBlobsWithPrefix(GetPrefix(path)).OfType<CloudBlockBlob>()) {
|
||||
|
||||
string prefix = String.Concat(Container.Name, "/", _shellName, "/", path);
|
||||
if ( !prefix.EndsWith("/") )
|
||||
prefix += "/";
|
||||
|
||||
foreach ( var blobItem in BlobClient.ListBlobsWithPrefix(prefix).OfType<CloudBlockBlob>() ) {
|
||||
yield return new AzureBlobFileStorage(blobItem);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IStorageFolder> ListFolders(string path)
|
||||
{
|
||||
public IEnumerable<IStorageFolder> ListFolders(string path) {
|
||||
EnsurePathIsRelative(path);
|
||||
if (!DirectoryExists(path))
|
||||
{
|
||||
path = String.Concat(_shellName, "/", path);
|
||||
|
||||
if ( !Container.DirectoryExists(path) ) {
|
||||
try {
|
||||
CreateFolder(path);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
catch ( Exception ex ) {
|
||||
throw new ArgumentException(string.Format("The folder could not be created at path: {0}. {1}", path, ex));
|
||||
}
|
||||
}
|
||||
@@ -134,27 +77,33 @@ namespace Orchard.Azure.Storage
|
||||
.OfType<CloudBlobDirectory>()
|
||||
.Select<CloudBlobDirectory, IStorageFolder>(d => new AzureBlobFolderStorage(d))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateFolder(string path) {
|
||||
EnsurePathIsRelative(path);
|
||||
EnsureDirectoryDoesNotExist(path);
|
||||
path = String.Concat(_shellName, "/", path);
|
||||
|
||||
Container.EnsureDirectoryDoesNotExist(path);
|
||||
Container.GetDirectoryReference(path);
|
||||
}
|
||||
|
||||
public void DeleteFolder(string path) {
|
||||
EnsureDirectoryExists(path);
|
||||
foreach (var blob in Container.GetDirectoryReference(path).ListBlobs()) {
|
||||
if (blob is CloudBlob)
|
||||
((CloudBlob)blob).Delete();
|
||||
EnsurePathIsRelative(path);
|
||||
path = String.Concat(_shellName, "/", path);
|
||||
|
||||
if (blob is CloudBlobDirectory)
|
||||
DeleteFolder(blob.Uri.ToString());
|
||||
Container.EnsureDirectoryExists(path);
|
||||
foreach ( var blob in Container.GetDirectoryReference(path).ListBlobs() ) {
|
||||
if ( blob is CloudBlob )
|
||||
( (CloudBlob)blob ).Delete();
|
||||
|
||||
if ( blob is CloudBlobDirectory )
|
||||
DeleteFolder(blob.Uri.ToString().Substring(Container.Uri.ToString().Length + 2 + _shellName.Length));
|
||||
}
|
||||
}
|
||||
|
||||
public void RenameFolder(string path, string newPath) {
|
||||
EnsurePathIsRelative(path);
|
||||
|
||||
EnsurePathIsRelative(newPath);
|
||||
|
||||
if ( !path.EndsWith("/") )
|
||||
@@ -163,7 +112,7 @@ namespace Orchard.Azure.Storage
|
||||
if ( !newPath.EndsWith("/") )
|
||||
newPath += "/";
|
||||
|
||||
foreach ( var blob in Container.GetDirectoryReference(path).ListBlobs() ) {
|
||||
foreach ( var blob in Container.GetDirectoryReference(_shellName + "/" + path).ListBlobs() ) {
|
||||
if ( blob is CloudBlob ) {
|
||||
string filename = Path.GetFileName(blob.Uri.ToString());
|
||||
string source = String.Concat(path, filename);
|
||||
@@ -183,16 +132,22 @@ namespace Orchard.Azure.Storage
|
||||
|
||||
public void DeleteFile(string path) {
|
||||
EnsurePathIsRelative(path);
|
||||
EnsureBlobExists(path);
|
||||
path = String.Concat(_shellName, "/", path);
|
||||
|
||||
Container.EnsureBlobExists(path);
|
||||
var blob = Container.GetBlockBlobReference(path);
|
||||
blob.Delete();
|
||||
}
|
||||
|
||||
public void RenameFile(string path, string newPath) {
|
||||
EnsurePathIsRelative(path);
|
||||
path = String.Concat(_shellName, "/", path);
|
||||
|
||||
EnsurePathIsRelative(newPath);
|
||||
EnsureBlobExists(path);
|
||||
EnsureBlobDoesNotExist(newPath);
|
||||
newPath = String.Concat(_shellName, "/", newPath);
|
||||
|
||||
Container.EnsureBlobExists(path);
|
||||
Container.EnsureBlobDoesNotExist(newPath);
|
||||
|
||||
var blob = Container.GetBlockBlobReference(path);
|
||||
var newBlob = Container.GetBlockBlobReference(newPath);
|
||||
@@ -202,7 +157,9 @@ namespace Orchard.Azure.Storage
|
||||
|
||||
public IStorageFile CreateFile(string path) {
|
||||
EnsurePathIsRelative(path);
|
||||
if (BlobExists(path)) {
|
||||
path = String.Concat(_shellName, "/", path);
|
||||
|
||||
if ( Container.BlobExists(path) ) {
|
||||
throw new ArgumentException("File " + path + " already exists");
|
||||
}
|
||||
|
||||
@@ -211,27 +168,6 @@ namespace Orchard.Azure.Storage
|
||||
return new AzureBlobFileStorage(blob);
|
||||
}
|
||||
|
||||
public string Combine(string path1, string path2) {
|
||||
EnsurePathIsRelative(path1);
|
||||
EnsurePathIsRelative(path2);
|
||||
|
||||
if (path1 == null || path2 == null)
|
||||
throw new ArgumentException("One or more path is null");
|
||||
|
||||
if (path1.Trim() == String.Empty)
|
||||
return path2;
|
||||
|
||||
if (path2.Trim() == String.Empty)
|
||||
return path1;
|
||||
|
||||
var uri1 = new Uri(path1);
|
||||
var uri2 = new Uri(path2);
|
||||
|
||||
return uri2.IsAbsoluteUri ? uri2.ToString() : new Uri(uri1, uri2).ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private class AzureBlobFileStorage : IStorageFile {
|
||||
private readonly CloudBlockBlob _blob;
|
||||
|
||||
@@ -276,8 +212,6 @@ namespace Orchard.Azure.Storage
|
||||
_blob = blob;
|
||||
}
|
||||
|
||||
#region IStorageFolder Members
|
||||
|
||||
public string GetName() {
|
||||
return _blob.Uri.ToString();
|
||||
}
|
||||
@@ -291,7 +225,7 @@ namespace Orchard.Azure.Storage
|
||||
}
|
||||
|
||||
public IStorageFolder GetParent() {
|
||||
if (_blob.Parent != null) {
|
||||
if ( _blob.Parent != null ) {
|
||||
return new AzureBlobFolderStorage(_blob.Parent);
|
||||
}
|
||||
throw new ArgumentException("Directory " + _blob.Uri + " does not have a parent directory");
|
||||
@@ -300,18 +234,17 @@ namespace Orchard.Azure.Storage
|
||||
private static long GetDirectorySize(CloudBlobDirectory directoryBlob) {
|
||||
long size = 0;
|
||||
|
||||
foreach (var blobItem in directoryBlob.ListBlobs()) {
|
||||
if (blobItem is CloudBlob)
|
||||
size += ((CloudBlob)blobItem).Properties.Length;
|
||||
foreach ( var blobItem in directoryBlob.ListBlobs() ) {
|
||||
if ( blobItem is CloudBlob )
|
||||
size += ( (CloudBlob)blobItem ).Properties.Length;
|
||||
|
||||
if (blobItem is CloudBlobDirectory)
|
||||
if ( blobItem is CloudBlobDirectory )
|
||||
size += GetDirectorySize((CloudBlobDirectory)blobItem);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -3,9 +3,21 @@
|
||||
As a root Orchard system operator
|
||||
I want to install and enable modules and enable features
|
||||
|
||||
Scenario: Default modules are listed
|
||||
Scenario: Installed modules are listed
|
||||
Given I have installed Orchard
|
||||
When I go to "admin/modules"
|
||||
Then I should see "Installed Modules"
|
||||
Then I should see "<h2>Installed Modules</h2>"
|
||||
And I should see "<h3>Themes</h3>"
|
||||
And the status should be 200 OK
|
||||
|
||||
Scenario: Edit module shows its features
|
||||
Given I have installed Orchard
|
||||
When I go to "admin/modules/Edit/Orchard.Themes"
|
||||
Then I should see "<h1>Edit Module: Themes</h1>"
|
||||
And the status should be 200 OK
|
||||
|
||||
Scenario: Features of installed modules are listed
|
||||
Given I have installed Orchard
|
||||
When I go to "admin/modules/features"
|
||||
Then I should see "<h2>Available Features</h2>"
|
||||
And the status should be 200 OK
|
46
src/Orchard.Specs/Modules.feature.cs
generated
46
src/Orchard.Specs/Modules.feature.cs
generated
@@ -51,10 +51,10 @@ namespace Orchard.Specs
|
||||
}
|
||||
|
||||
[NUnit.Framework.TestAttribute()]
|
||||
[NUnit.Framework.DescriptionAttribute("Default modules are listed")]
|
||||
public virtual void DefaultModulesAreListed()
|
||||
[NUnit.Framework.DescriptionAttribute("Installed modules are listed")]
|
||||
public virtual void InstalledModulesAreListed()
|
||||
{
|
||||
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Default modules are listed", ((string[])(null)));
|
||||
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Installed modules are listed", ((string[])(null)));
|
||||
#line 6
|
||||
this.ScenarioSetup(scenarioInfo);
|
||||
#line 7
|
||||
@@ -62,11 +62,49 @@ this.ScenarioSetup(scenarioInfo);
|
||||
#line 8
|
||||
testRunner.When("I go to \"admin/modules\"");
|
||||
#line 9
|
||||
testRunner.Then("I should see \"Installed Modules\"");
|
||||
testRunner.Then("I should see \"<h2>Installed Modules</h2>\"");
|
||||
#line 10
|
||||
testRunner.And("I should see \"<h3>Themes</h3>\"");
|
||||
#line 11
|
||||
testRunner.And("the status should be 200 OK");
|
||||
#line hidden
|
||||
testRunner.CollectScenarioErrors();
|
||||
}
|
||||
|
||||
[NUnit.Framework.TestAttribute()]
|
||||
[NUnit.Framework.DescriptionAttribute("Edit module shows its features")]
|
||||
public virtual void EditModuleShowsItsFeatures()
|
||||
{
|
||||
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Edit module shows its features", ((string[])(null)));
|
||||
#line 13
|
||||
this.ScenarioSetup(scenarioInfo);
|
||||
#line 14
|
||||
testRunner.Given("I have installed Orchard");
|
||||
#line 15
|
||||
testRunner.When("I go to \"admin/modules/Edit/Orchard.Themes\"");
|
||||
#line 16
|
||||
testRunner.Then("I should see \"<h1>Edit Module: Themes</h1>\"");
|
||||
#line 17
|
||||
testRunner.And("the status should be 200 OK");
|
||||
#line hidden
|
||||
testRunner.CollectScenarioErrors();
|
||||
}
|
||||
|
||||
[NUnit.Framework.TestAttribute()]
|
||||
[NUnit.Framework.DescriptionAttribute("Features of installed modules are listed")]
|
||||
public virtual void FeaturesOfInstalledModulesAreListed()
|
||||
{
|
||||
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Features of installed modules are listed", ((string[])(null)));
|
||||
#line 19
|
||||
this.ScenarioSetup(scenarioInfo);
|
||||
#line 20
|
||||
testRunner.Given("I have installed Orchard");
|
||||
#line 21
|
||||
testRunner.When("I go to \"admin/modules/features\"");
|
||||
#line 22
|
||||
testRunner.Then("I should see \"<h2>Available Features</h2>\"");
|
||||
#line 23
|
||||
testRunner.And("the status should be 200 OK");
|
||||
#line hidden
|
||||
testRunner.CollectScenarioErrors();
|
||||
}
|
||||
|
@@ -105,10 +105,6 @@ namespace Orchard.Tests.Environment {
|
||||
}
|
||||
}
|
||||
|
||||
public Feature LoadFeature(FeatureDescriptor featureDescriptor) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IEnumerable<ExtensionEntry> ActiveExtensions_Obsolete() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
@@ -256,7 +256,7 @@ features:
|
||||
[Test]
|
||||
public void ExtensionManagerShouldThrowIfFeatureDoesNotExist() {
|
||||
var featureDescriptor = new FeatureDescriptor { Name = "NoSuchFeature" };
|
||||
Assert.Throws<ArgumentException>(() => _manager.LoadFeature(featureDescriptor));
|
||||
Assert.Throws<ArgumentException>(() => _manager.LoadFeatures(new [] { featureDescriptor }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -302,8 +302,10 @@ features:
|
||||
.SelectMany(x => x.Features)
|
||||
.Single(x => x.Name == "TestFeature");
|
||||
|
||||
foreach (var type in extensionManager.LoadFeature(testFeature).ExportedTypes) {
|
||||
Assert.That(type == typeof(Phi));
|
||||
foreach (var feature in extensionManager.LoadFeatures(new[] { testFeature })) {
|
||||
foreach (var type in feature.ExportedTypes) {
|
||||
Assert.That(type == typeof(Phi));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,9 +330,11 @@ features:
|
||||
.SelectMany(x => x.Features)
|
||||
.Single(x => x.Name == "TestModule");
|
||||
|
||||
foreach (var type in extensionManager.LoadFeature(testModule).ExportedTypes) {
|
||||
Assert.That(type != typeof(Phi));
|
||||
Assert.That((type == typeof(Alpha) || (type == typeof(Beta))));
|
||||
foreach (var feature in extensionManager.LoadFeatures(new [] { testModule })) {
|
||||
foreach (var type in feature.ExportedTypes) {
|
||||
Assert.That(type != typeof(Phi));
|
||||
Assert.That((type == typeof(Alpha) || (type == typeof(Beta))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -68,10 +68,6 @@ namespace Orchard.Tests.Mvc.Routes {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Feature LoadFeature(FeatureDescriptor featureDescriptor) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IEnumerable<ExtensionEntry> ActiveExtensions_Obsolete() {
|
||||
yield return new ExtensionEntry {
|
||||
Descriptor = new ExtensionDescriptor {
|
||||
@@ -87,11 +83,6 @@ namespace Orchard.Tests.Mvc.Routes {
|
||||
};
|
||||
}
|
||||
|
||||
public Feature LoadFeature(string featureName) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
public void InstallExtension(string extensionType, HttpPostedFileBase extensionBundle) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
@@ -8,7 +8,9 @@ namespace Orchard.Modules {
|
||||
builder.Add("Modules", "10",
|
||||
menu => menu
|
||||
.Add("Manage Modules", "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.Modules" })
|
||||
.Permission(Permissions.ManageModules)));
|
||||
.Permission(Permissions.ManageModules))
|
||||
.Add("Manage Features", "2.0", item => item.Action("Features", "Admin", new { area = "Orchard.Modules" })
|
||||
.Permission(Permissions.ManageFeatures)));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,17 +1,50 @@
|
||||
using System.Web.Mvc;
|
||||
using System;
|
||||
using System.Web.Mvc;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Modules.ViewModels;
|
||||
using Orchard.Mvc.Results;
|
||||
|
||||
namespace Orchard.Modules.Controllers {
|
||||
public class AdminController : Controller {
|
||||
private readonly IModuleService _moduleService;
|
||||
|
||||
public AdminController(IModuleService moduleService) {
|
||||
public AdminController(IOrchardServices services, IModuleService moduleService) {
|
||||
Services = services;
|
||||
_moduleService = moduleService;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
private Localizer T { get; set; }
|
||||
public IOrchardServices Services { get; set; }
|
||||
|
||||
public ActionResult Index() {
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageModules, T("Not allowed to manage modules")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
var modules = _moduleService.GetInstalledModules();
|
||||
return View(new ModulesIndexViewModel {Modules = modules});
|
||||
}
|
||||
|
||||
public ActionResult Edit(string moduleName) {
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageModules, T("Not allowed to edit module")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
var module = _moduleService.GetModuleByName(moduleName);
|
||||
|
||||
if (module == null)
|
||||
return new NotFoundResult();
|
||||
|
||||
return View(new ModuleEditViewModel {
|
||||
Name = module.DisplayName
|
||||
});
|
||||
}
|
||||
|
||||
public ActionResult Features() {
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageFeatures, T("Not allowed to manage features")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
var features = _moduleService.GetAvailableFeatures();
|
||||
return View(new FeatureListViewModel {Features = features});
|
||||
}
|
||||
}
|
||||
}
|
@@ -63,10 +63,14 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="AdminMenu.cs" />
|
||||
<Compile Include="Controllers\AdminController.cs" />
|
||||
<Compile Include="ViewModels\FeatureListViewModel.cs" />
|
||||
<Compile Include="Models\Module.cs" />
|
||||
<Compile Include="Permissions.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Routes.cs" />
|
||||
<Compile Include="Routing\ModuleNameConstraint.cs" />
|
||||
<Compile Include="Services\ModuleService.cs" />
|
||||
<Compile Include="ViewModels\ModuleEditViewModel.cs" />
|
||||
<Compile Include="ViewModels\ModulesIndexViewModel.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -81,8 +85,14 @@
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\Admin\Features.ascx" />
|
||||
<Content Include="Views\Admin\Edit.ascx" />
|
||||
<Content Include="Views\Web.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Views\DisplayTemplates\Items\" />
|
||||
<Folder Include="Views\DisplayTemplates\Parts\" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v9.0\WebApplications\Microsoft.WebApplication.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
|
@@ -4,13 +4,14 @@ using Orchard.Security.Permissions;
|
||||
namespace Orchard.Modules {
|
||||
public class Permissions : IPermissionProvider {
|
||||
public static readonly Permission ManageModules = new Permission { Description = "Manage Modules", Name = "ManageModules" };
|
||||
public static readonly Permission ManageFeatures = new Permission { Description = "Manage Features", Name = "ManageFeatures", ImpliedBy = new[] {ManageModules}};
|
||||
|
||||
public string ModuleName {
|
||||
get { return "Modules"; }
|
||||
}
|
||||
|
||||
public IEnumerable<Permission> GetPermissions() {
|
||||
return new[] {ManageModules};
|
||||
return new[] {ManageModules, ManageFeatures};
|
||||
}
|
||||
|
||||
public IEnumerable<PermissionStereotype> GetDefaultStereotypes() {
|
||||
|
41
src/Orchard.Web/Modules/Orchard.Modules/Routes.cs
Normal file
41
src/Orchard.Web/Modules/Orchard.Modules/Routes.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using Orchard.Modules.Routing;
|
||||
using Orchard.Mvc.Routes;
|
||||
|
||||
namespace Orchard.Modules {
|
||||
public class Routes : IRouteProvider {
|
||||
private readonly IModuleNameConstraint _moduleNameConstraint;
|
||||
|
||||
public Routes(IModuleNameConstraint moduleNameConstraint) {
|
||||
_moduleNameConstraint = moduleNameConstraint;
|
||||
}
|
||||
|
||||
public IEnumerable<RouteDescriptor> GetRoutes() {
|
||||
return new[] {
|
||||
new RouteDescriptor {
|
||||
Route = new Route(
|
||||
"Admin/Modules/Edit/{moduleName}",
|
||||
new RouteValueDictionary {
|
||||
{"area", "Orchard.Modules"},
|
||||
{"controller", "Admin"},
|
||||
{"action", "Edit"}
|
||||
},
|
||||
new RouteValueDictionary {
|
||||
{"moduleName", _moduleNameConstraint}
|
||||
},
|
||||
new RouteValueDictionary {
|
||||
{"area", "Orchard.Modules"}
|
||||
},
|
||||
new MvcRouteHandler())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void GetRoutes(ICollection<RouteDescriptor> routes) {
|
||||
foreach (var routeDescriptor in GetRoutes())
|
||||
routes.Add(routeDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using System.Web.Routing;
|
||||
|
||||
namespace Orchard.Modules.Routing {
|
||||
public interface IModuleNameConstraint : IRouteConstraint, ISingletonDependency {
|
||||
}
|
||||
|
||||
public class ModuleNameConstraint : IModuleNameConstraint {
|
||||
private readonly IModuleService _moduleService;
|
||||
|
||||
public ModuleNameConstraint(IModuleService moduleService) {
|
||||
_moduleService = moduleService;
|
||||
}
|
||||
|
||||
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) {
|
||||
if (routeDirection == RouteDirection.UrlGeneration)
|
||||
return true;
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(parameterName, out value))
|
||||
return _moduleService.GetModuleByName(Convert.ToString(value)) != null;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,10 +3,12 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.Modules.Models;
|
||||
|
||||
namespace Orchard.Modules.Services {
|
||||
public class ModuleService : IModuleService {
|
||||
private const string ModuleExtensionType = "module";
|
||||
private readonly IExtensionManager _extensionManager;
|
||||
|
||||
public ModuleService(IExtensionManager extensionManager) {
|
||||
@@ -14,28 +16,56 @@ namespace Orchard.Modules.Services {
|
||||
}
|
||||
|
||||
public IModule GetModuleByName(string moduleName) {
|
||||
return null;
|
||||
return _extensionManager.AvailableExtensions().Where(e => string.Equals(e.Name, moduleName, StringComparison.OrdinalIgnoreCase) && string.Equals(e.ExtensionType, "Module", StringComparison.OrdinalIgnoreCase)).Select(
|
||||
descriptor => AssembleModuleFromDescriptor(descriptor)).FirstOrDefault();
|
||||
}
|
||||
|
||||
public IEnumerable<IModule> GetInstalledModules() {
|
||||
return
|
||||
_extensionManager.AvailableExtensions().Where(
|
||||
e => String.Equals(e.ExtensionType, "Module", StringComparison.OrdinalIgnoreCase)).Select(
|
||||
descriptor => (new Module {
|
||||
ModuleName = descriptor.Name,
|
||||
DisplayName = descriptor.DisplayName,
|
||||
Description = descriptor.Description,
|
||||
Version = descriptor.Version,
|
||||
Author = descriptor.Author,
|
||||
HomePage = descriptor.WebSite,
|
||||
Tags = descriptor.Tags
|
||||
}) as IModule);
|
||||
e => String.Equals(e.ExtensionType, ModuleExtensionType, StringComparison.OrdinalIgnoreCase)).Select(
|
||||
descriptor => AssembleModuleFromDescriptor(descriptor));
|
||||
}
|
||||
|
||||
public void InstallModule(HttpPostedFileBase file) {
|
||||
_extensionManager.InstallExtension(ModuleExtensionType, file);
|
||||
}
|
||||
|
||||
public void UninstallModule(string moduleName) {
|
||||
_extensionManager.UninstallExtension(ModuleExtensionType, moduleName);
|
||||
}
|
||||
|
||||
public IEnumerable<Feature> GetAvailableFeatures() {
|
||||
return GetInstalledModules()
|
||||
.Where(m => m.Features != null)
|
||||
.SelectMany(m => _extensionManager.LoadFeatures(m.Features));
|
||||
}
|
||||
|
||||
public IEnumerable<Feature> GetAvailableFeaturesByModule(string moduleName) {
|
||||
var module = GetModuleByName(moduleName);
|
||||
if (module == null || module.Features == null)
|
||||
return null;
|
||||
|
||||
return _extensionManager.LoadFeatures(module.Features);
|
||||
}
|
||||
|
||||
public void EnableFeatures(IEnumerable<string> featureNames) {
|
||||
}
|
||||
|
||||
public void DisableFeatures(IEnumerable<string> featureNames) {
|
||||
}
|
||||
|
||||
private static IModule AssembleModuleFromDescriptor(ExtensionDescriptor extensionDescriptor) {
|
||||
return new Module {
|
||||
ModuleName = extensionDescriptor.Name,
|
||||
DisplayName = extensionDescriptor.DisplayName,
|
||||
Description = extensionDescriptor.Description,
|
||||
Version = extensionDescriptor.Version,
|
||||
Author = extensionDescriptor.Author,
|
||||
HomePage = extensionDescriptor.WebSite,
|
||||
Tags = extensionDescriptor.Tags,
|
||||
Features = extensionDescriptor.Features
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.Mvc.ViewModels;
|
||||
|
||||
namespace Orchard.Modules.ViewModels {
|
||||
public class FeatureListViewModel : BaseViewModel {
|
||||
public IEnumerable<Feature> Features { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
using Orchard.Mvc.ViewModels;
|
||||
|
||||
namespace Orchard.Modules.ViewModels {
|
||||
public class ModuleEditViewModel : BaseViewModel {
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<ModuleEditViewModel>" %>
|
||||
<%@ Import Namespace="Orchard.Mvc.Html"%>
|
||||
<%@ Import Namespace="Orchard.Modules.ViewModels"%>
|
||||
<h1><%=Html.TitleForPage(T("Edit Module: {0}", Model.Name).ToString()) %></h1>
|
||||
<p><%=_Encoded("Edit the module. Maybe show module's features.") %></p>
|
@@ -0,0 +1,33 @@
|
||||
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<FeatureListViewModel>" %>
|
||||
<%@ Import Namespace="Orchard.Mvc.Html"%>
|
||||
<%@ Import Namespace="Orchard.Modules.ViewModels"%>
|
||||
<h1><%=Html.TitleForPage(T("Manage Features").ToString()) %></h1>
|
||||
<h2><%=T("Available Features") %></h2>
|
||||
<% if (Model.Features.Count() > 0) { %>
|
||||
<ul class="contentItems blogs"><%
|
||||
foreach (var featureGroup in Model.Features.OrderBy(f => f.Descriptor.Name).GroupBy(f => f.Descriptor.Category)) { %>
|
||||
<li>
|
||||
<h3><%=Html.Encode(featureGroup.First().Descriptor.Category ?? T("General")) %></h3>
|
||||
<ul><%
|
||||
foreach (var feature in featureGroup.Select(f => f.Descriptor)) {%>
|
||||
<li>
|
||||
<h4><%=Html.Encode(feature.Name) %></h4>
|
||||
<p><%=T("From: {0}", Html.Encode(feature.Extension.DisplayName)) %></p><%
|
||||
if (!string.IsNullOrEmpty(feature.Description)) { %>
|
||||
<p><%=Html.Encode(feature.Description) %></p><%
|
||||
}
|
||||
if (feature.Dependencies.Count() > 0) {%>
|
||||
<h5><%=_Encoded("Depends on:")%></h5>
|
||||
<ul><%
|
||||
foreach (var dependency in feature.Dependencies) { %>
|
||||
<li><%=Html.Encode(dependency) %></li><%
|
||||
} %>
|
||||
</ul><%
|
||||
}%>
|
||||
</li><%
|
||||
} %>
|
||||
</ul>
|
||||
</li><%
|
||||
} %>
|
||||
</ul><%
|
||||
} %>
|
@@ -4,11 +4,21 @@
|
||||
<h1><%=Html.TitleForPage(T("Manage Modules").ToString()) %></h1>
|
||||
<h2><%=T("Installed Modules") %></h2>
|
||||
<% if (Model.Modules.Count() > 0) { %>
|
||||
<ul><%
|
||||
<ul class="contentItems blogs"><%
|
||||
foreach (var module in Model.Modules.OrderBy(m => m.DisplayName)) { %>
|
||||
<li>
|
||||
<h3><%=Html.Encode(module.DisplayName) %></h3>
|
||||
<p><%=module.Description != null ? Html.Encode(module.Description) : T("<em>no description</em>") %></p>
|
||||
<div class="summary">
|
||||
<div class="properties">
|
||||
<h3><%=Html.Encode(module.DisplayName) %></h3>
|
||||
<div class="related">
|
||||
<%=Html.ActionLink(T("Edit").ToString(), "edit", new {moduleName = module.ModuleName, area = "Orchard.Modules"}) %><%=_Encoded(" | ")%>
|
||||
<a href="#">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</div><%
|
||||
if (!string.IsNullOrEmpty(module.Description)) { %>
|
||||
<p><%=Html.Encode(module.Description) %></p><%
|
||||
} %>
|
||||
</li><%
|
||||
} %>
|
||||
</ul><%
|
||||
|
@@ -84,7 +84,7 @@ namespace Orchard.Setup.Controllers {
|
||||
DataTablePrefix = model.DatabaseTablePrefix,
|
||||
};
|
||||
|
||||
// The vanilla Orchard distibution has the following modules enabled.
|
||||
// The vanilla Orchard distibution has the following features enabled.
|
||||
const string hardcoded =
|
||||
@"Orchard.Framework,
|
||||
Common,Dashboard,Feeds,HomePage,Navigation,Scheduling,Settings,XmlRpc,
|
||||
|
@@ -17,7 +17,6 @@ namespace Orchard.Environment.Extensions {
|
||||
private readonly IEnumerable<IExtensionFolders> _folders;
|
||||
private readonly IEnumerable<IExtensionLoader> _loaders;
|
||||
private IEnumerable<ExtensionEntry> _activeExtensions;
|
||||
//private readonly IRepository<ExtensionRecord> _extensionRepository;
|
||||
|
||||
public Localizer T { get; set; }
|
||||
public ILogger Logger { get; set; }
|
||||
@@ -25,11 +24,9 @@ namespace Orchard.Environment.Extensions {
|
||||
public ExtensionManager(
|
||||
IEnumerable<IExtensionFolders> folders,
|
||||
IEnumerable<IExtensionLoader> loaders
|
||||
//IRepository<ExtensionRecord> extensionRepository
|
||||
) {
|
||||
_folders = folders;
|
||||
_loaders = loaders.OrderBy(x => x.Order);
|
||||
//_extensionRepository = extensionRepository;
|
||||
T = NullLocalizer.Instance;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
@@ -119,7 +116,7 @@ namespace Orchard.Environment.Extensions {
|
||||
return featureDescriptors;
|
||||
}
|
||||
|
||||
public Feature LoadFeature(FeatureDescriptor featureDescriptor) {
|
||||
private Feature LoadFeature(FeatureDescriptor featureDescriptor) {
|
||||
var featureName = featureDescriptor.Name;
|
||||
string extensionName = GetExtensionForFeature(featureName);
|
||||
if (extensionName == null) throw new ArgumentException(T("Feature ") + featureName + T(" was not found in any of the installed extensions"));
|
||||
@@ -224,7 +221,6 @@ namespace Orchard.Environment.Extensions {
|
||||
|
||||
private IEnumerable<ExtensionEntry> BuildActiveExtensions() {
|
||||
foreach (var descriptor in AvailableExtensions()) {
|
||||
//_extensionRepository.Create(new ExtensionRecord { Name = descriptor.Name });
|
||||
// Extensions that are Themes don't have buildable components.
|
||||
if (String.Equals(descriptor.ExtensionType, "Module", StringComparison.OrdinalIgnoreCase)) {
|
||||
yield return BuildEntry(descriptor);
|
||||
@@ -232,15 +228,7 @@ namespace Orchard.Environment.Extensions {
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsExtensionEnabled(string name) {
|
||||
//ExtensionRecord extensionRecord = _extensionRepository.Get(x => x.Name == name);
|
||||
//if (extensionRecord.Enabled) return true;
|
||||
//return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private ExtensionEntry BuildEntry(ExtensionDescriptor descriptor) {
|
||||
if (!IsExtensionEnabled(descriptor.Name)) return null;
|
||||
foreach (var loader in _loaders) {
|
||||
var entry = loader.Load(descriptor);
|
||||
if (entry != null)
|
||||
|
@@ -6,10 +6,10 @@ namespace Orchard.Environment.Extensions {
|
||||
public interface IExtensionManager {
|
||||
IEnumerable<ExtensionDescriptor> AvailableExtensions();
|
||||
IEnumerable<Feature> LoadFeatures(IEnumerable<FeatureDescriptor> featureDescriptors);
|
||||
Feature LoadFeature(FeatureDescriptor featureDescriptor);
|
||||
|
||||
IEnumerable<ExtensionEntry> ActiveExtensions_Obsolete();
|
||||
void InstallExtension(string extensionType, HttpPostedFileBase extensionBundle);
|
||||
void UninstallExtension(string extensionType, string extensionName);
|
||||
|
||||
// Used by the HackInstallationGenerator and will be removed along with it.
|
||||
IEnumerable<ExtensionEntry> ActiveExtensions_Obsolete();
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Web;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
|
||||
namespace Orchard.Modules {
|
||||
public interface IModuleService : IDependency {
|
||||
@@ -7,5 +8,8 @@ namespace Orchard.Modules {
|
||||
IEnumerable<IModule> GetInstalledModules();
|
||||
void InstallModule(HttpPostedFileBase file);
|
||||
void UninstallModule(string moduleName);
|
||||
IEnumerable<Feature> GetAvailableFeatures();
|
||||
void EnableFeatures(IEnumerable<string> featureNames);
|
||||
void DisableFeatures(IEnumerable<string> featureNames);
|
||||
}
|
||||
}
|
@@ -52,10 +52,10 @@ namespace Orchard.Mvc.ViewEngines {
|
||||
}
|
||||
|
||||
|
||||
var modules = _extensionManager.ActiveExtensions_Obsolete()
|
||||
.Where(x => x.Descriptor.ExtensionType == "Module");
|
||||
var modules = _extensionManager.AvailableExtensions()
|
||||
.Where(x => x.ExtensionType == "Module");
|
||||
|
||||
var moduleLocations = modules.Select(x => Path.Combine(x.Descriptor.Location, x.Descriptor.Name));
|
||||
var moduleLocations = modules.Select(x => Path.Combine(x.Location, x.Name));
|
||||
var moduleViewEngines = _viewEngineProviders
|
||||
.Select(x => x.CreateModulesViewEngine(new CreateModulesViewEngineParams { VirtualPaths = moduleLocations }));
|
||||
Logger.Debug("Module locations:\r\n\t-{0}", string.Join("\r\n\t-", moduleLocations.ToArray()));
|
||||
|
@@ -11,14 +11,5 @@ namespace Orchard.Storage {
|
||||
void DeleteFile(string path);
|
||||
void RenameFile(string path, string newPath);
|
||||
IStorageFile CreateFile(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Combines two path strings
|
||||
/// </summary>
|
||||
/// <param name="path1">The first path</param>
|
||||
/// <param name="path2">The second path</param>
|
||||
/// <returns>A string containing the combined paths. If one of the specified paths is a zero-length string, this method returns the other path.
|
||||
/// If path2 contains an absolute path, this method returns path2.</returns>
|
||||
string Combine(string path1, string path2);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user