Moving ModuleService functionality to FeatureManager. Adding UT and comments. Removing Recipe module dependency on Modules module.

--HG--
branch : dev
This commit is contained in:
Andre Rodrigues
2011-03-14 11:14:35 -07:00
parent f3cc4bd77e
commit 88e347a69a
15 changed files with 588 additions and 228 deletions

View File

@@ -132,7 +132,7 @@ namespace Orchard.Tests.Modules.Media.Services {
Record = new MediaSettingsPartRecord { UploadAllowedFileTypeWhitelist = "txt dll config" }
};
StubWorkContextAccessor.WorkContextImpl.InitMethod = workContext => {
StubWorkContextAccessor.WorkContextImpl._initMethod = workContext => {
workContext.CurrentSite.ContentItem.Weld(mediaSettingsPart);
};
@@ -158,7 +158,7 @@ namespace Orchard.Tests.Modules.Media.Services {
Record = new MediaSettingsPartRecord { UploadAllowedFileTypeWhitelist = "txt dll config" }
};
StubWorkContextAccessor.WorkContextImpl.InitMethod = workContext => {
StubWorkContextAccessor.WorkContextImpl._initMethod = workContext => {
workContext.CurrentSite.ContentItem.Weld(mediaSettingsPart);
};

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Autofac;
using NHibernate;
using NUnit.Framework;
using Orchard.Data;
using Orchard.Services;
using Orchard.Tests.Data;
using Orchard.Tests.Stubs;
namespace Orchard.Tests {
public abstract class DatabaseEnabledTestsBase {
protected IContainer _container;
protected ISession _session;
protected string _databaseFilePath;
protected ISessionFactory _sessionFactory;
protected StubClock _clock;
[TestFixtureSetUp]
public void InitFixture() {
_databaseFilePath = Path.GetTempFileName();
}
[TestFixtureTearDown]
public void TearDownFixture() {
File.Delete(_databaseFilePath);
}
[SetUp]
public virtual void Init() {
_sessionFactory = DataUtility.CreateSessionFactory(_databaseFilePath, DatabaseTypes.ToArray());
_session = _sessionFactory.OpenSession();
_clock = new StubClock();
var builder = new ContainerBuilder();
//builder.RegisterModule(new ImplicitCollectionSupportModule());
builder.RegisterInstance(new StubLocator(_session)).As<ISessionLocator>();
builder.RegisterInstance(_clock).As<IClock>();
builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>));
Register(builder);
_container = builder.Build();
}
[TearDown]
public void Cleanup() {
if(_container != null)
_container.Dispose();
if(_session != null)
_session.Close();
}
public abstract void Register(ContainerBuilder builder);
protected virtual IEnumerable<Type> DatabaseTypes {
get {
return Enumerable.Empty<Type>();
}
}
protected void ClearSession() {
Trace.WriteLine("Flush and clear session");
_session.Flush();
_session.Clear();
Trace.WriteLine("Flushed and cleared session");
}
}
}

View File

@@ -0,0 +1,188 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Autofac;
using NUnit.Framework;
using Orchard.Caching;
using Orchard.Core.Settings.Descriptor;
using Orchard.Core.Settings.Descriptor.Records;
using Orchard.Core.Settings.State;
using Orchard.Environment.Descriptor;
using Orchard.Environment.Descriptor.Models;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Folders;
using Orchard.Environment.Features;
using Orchard.Environment.State;
using Orchard.Events;
using Orchard.Tests.Environment.Extensions;
using Orchard.Tests.Stubs;
namespace Orchard.Tests.Environment.Features {
[TestFixture]
public class FeatureManagerTests : DatabaseEnabledTestsBase {
private ExtensionManagerTests.StubFolders _folders;
protected override IEnumerable<Type> DatabaseTypes {
get {
return new[] {
typeof (ShellDescriptorRecord),
typeof (ShellFeatureRecord),
typeof (ShellParameterRecord),
};
}
}
public override void Register(ContainerBuilder builder) {
_folders = new ExtensionManagerTests.StubFolders();
builder.RegisterInstance(_folders).As<IExtensionFolders>();
builder.RegisterType<ExtensionManager>().As<IExtensionManager>();
builder.RegisterType<FeatureManager>().As<IFeatureManager>();
builder.RegisterType<StubCacheManager>().As<ICacheManager>();
builder.RegisterType<ShellDescriptorManager>().As<IShellDescriptorManager>().SingleInstance();
builder.RegisterType<ShellStateManager>().As<IShellStateManager>().SingleInstance();
builder.RegisterType<StubEventBus>().As<IEventBus>().SingleInstance();
builder.RegisterSource(new EventsRegistrationSource());
}
[Test]
public void EnableFeaturesTest() {
_folders.Manifests.Add("SuperWiki", @"
Name: SuperWiki
Version: 1.0.3
OrchardVersion: 1
Features:
SuperWiki:
Description: My super wiki module for Orchard.
");
// Initialize the shell descriptor with 0 features
IShellDescriptorManager shellDescriptorManager = _container.Resolve<IShellDescriptorManager>();
IFeatureManager featureManager = _container.Resolve<IFeatureManager>();
shellDescriptorManager.UpdateShellDescriptor(0,
Enumerable.Empty<ShellFeature>(),
Enumerable.Empty<ShellParameter>());
IEnumerable<string> featuresToEnable = new [] { "SuperWiki" };
// Enable all features
IEnumerable<string> enabledFeatures = featureManager.EnableFeatures(featuresToEnable);
Assert.That(enabledFeatures, Is.EqualTo(featuresToEnable));
Assert.That(featureManager.GetEnabledFeatures().Count(), Is.EqualTo(1));
}
[Test]
public void EnableFeaturesWithDependenciesTest() {
_folders.Manifests.Add("SuperWiki", @"
Name: SuperWiki
Version: 1.0.3
OrchardVersion: 1
Features:
SuperWiki:
Description: My super wiki module for Orchard.
Dependencies: SuperWikiDep
SuperWikiDep:
Description: My super wiki module for Orchard dependency.
");
// Initialize the shell descriptor with 0 features
IShellDescriptorManager shellDescriptorManager = _container.Resolve<IShellDescriptorManager>();
IFeatureManager featureManager = _container.Resolve<IFeatureManager>();
shellDescriptorManager.UpdateShellDescriptor(0,
Enumerable.Empty<ShellFeature>(),
Enumerable.Empty<ShellParameter>());
IEnumerable<string> featuresToEnable = new[] { "SuperWiki" };
// Try to enable without forcing dependencies should fail
IEnumerable<string> enabledFeatures = featureManager.EnableFeatures(featuresToEnable, false);
Assert.That(enabledFeatures.Count(), Is.EqualTo(0));
Assert.That(featureManager.GetEnabledFeatures().Count(), Is.EqualTo(0));
// Enabling while forcing dependencies should succeed.
enabledFeatures = featureManager.EnableFeatures(featuresToEnable, true);
Assert.That(enabledFeatures.Contains("SuperWiki"), Is.True);
Assert.That(enabledFeatures.Contains("SuperWikiDep"), Is.True);
Assert.That(featureManager.GetEnabledFeatures().Count(), Is.EqualTo(2));
}
[Test]
public void DisableFeaturesTest() {
_folders.Manifests.Add("SuperWiki", @"
Name: SuperWiki
Version: 1.0.3
OrchardVersion: 1
Features:
SuperWiki:
Description: My super wiki module for Orchard.
");
// Initialize the shell descriptor with 0 features
IShellDescriptorManager shellDescriptorManager = _container.Resolve<IShellDescriptorManager>();
IFeatureManager featureManager = _container.Resolve<IFeatureManager>();
shellDescriptorManager.UpdateShellDescriptor(0,
new [] { new ShellFeature { Name = "SuperWiki" } },
Enumerable.Empty<ShellParameter>());
IEnumerable<string> featuresToDisable = new [] { "SuperWiki" };
// Disable the feature
featureManager.DisableFeatures(featuresToDisable);
Assert.That(featureManager.GetEnabledFeatures().Count(), Is.EqualTo(0));
}
[Test]
public void DisableFeaturesWithDependenciesTest() {
_folders.Manifests.Add("SuperWiki", @"
Name: SuperWiki
Version: 1.0.3
OrchardVersion: 1
Features:
SuperWiki:
Description: My super wiki module for Orchard.
Dependencies: SuperWikiDep
SuperWikiDep:
Description: My super wiki module for Orchard dependency.
");
// Initialize the shell descriptor with 0 features
IShellDescriptorManager shellDescriptorManager = _container.Resolve<IShellDescriptorManager>();
IFeatureManager featureManager = _container.Resolve<IFeatureManager>();
shellDescriptorManager.UpdateShellDescriptor(0,
Enumerable.Empty<ShellFeature>(),
Enumerable.Empty<ShellParameter>());
// Enable both features by relying on the dependency
Assert.That(featureManager.EnableFeatures(new [] { "SuperWiki"}, true).Count(), Is.EqualTo(2));
IEnumerable<string> featuresToDisable = new[] { "SuperWikiDep" };
// Try to enable without forcing dependencies should fail
IEnumerable<string> disabledFeatures = featureManager.DisableFeatures(featuresToDisable, false);
Assert.That(disabledFeatures.Count(), Is.EqualTo(0));
Assert.That(featureManager.GetEnabledFeatures().Count(), Is.EqualTo(2));
// Enabling while forcing dependencies should succeed.
disabledFeatures = featureManager.DisableFeatures(featuresToDisable, true);
Assert.That(disabledFeatures.Contains("SuperWiki"), Is.True);
Assert.That(disabledFeatures.Contains("SuperWikiDep"), Is.True);
Assert.That(featureManager.GetEnabledFeatures().Count(), Is.EqualTo(0));
}
}
public class StubEventBus : IEventBus {
public string LastMessageName { get; set; }
public IDictionary<string, object> LastEventData { get; set; }
public IEnumerable Notify(string messageName, IDictionary<string, object> eventData) {
LastMessageName = messageName;
LastEventData = eventData;
return new object[0];
}
}
}

View File

@@ -207,6 +207,7 @@
<Compile Include="ContentManagement\Records\GammaRecord.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="DatabaseEnabledTestsBase.cs" />
<Compile Include="DataMigration\SchemaBuilderTests.cs" />
<Compile Include="DataMigration\DataMigrationTests.cs" />
<Compile Include="DataMigration\Utilities\NullInterpreter.cs" />
@@ -233,6 +234,7 @@
<Compile Include="Environment\AutofacUtil\DynamicProxy2\DynamicProxyTests.cs" />
<Compile Include="Environment\DefaultWorkContextAccessorTests.cs" />
<Compile Include="Environment\Extensions\ExtensionLoaderCoordinatorTests.cs" />
<Compile Include="Environment\Features\FeatureManagerTests.cs" />
<Compile Include="Environment\State\DefaultProcessingEngineTests.cs" />
<Compile Include="Environment\RunningShellTableTests.cs" />
<Compile Include="Environment\StubHostEnvironment.cs" />
@@ -304,6 +306,10 @@
<Compile Include="Utility\ReflectTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Orchard.Web\Core\Orchard.Core.csproj">
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
<Name>Orchard.Core</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard\Orchard.Framework.csproj">
<Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project>
<Name>Orchard.Framework</Name>

View File

@@ -9,7 +9,7 @@ using Orchard.Settings;
namespace Orchard.Tests.Stubs {
public class StubWorkContextAccessor : IWorkContextAccessor {
private readonly ILifetimeScope _lifetimeScope;
private WorkContext _workContext;
private readonly WorkContext _workContext;
public StubWorkContextAccessor(ILifetimeScope lifetimeScope) {
_lifetimeScope = lifetimeScope;
@@ -18,9 +18,11 @@ namespace Orchard.Tests.Stubs {
public class WorkContextImpl : WorkContext {
private readonly ILifetimeScope _lifetimeScope;
private Dictionary<string, object> _contextDictonary;
private readonly Dictionary<string, object> _contextDictonary;
public delegate void MyInitMethod(WorkContextImpl workContextImpl);
public static MyInitMethod InitMethod;
public static MyInitMethod _initMethod;
public WorkContextImpl(ILifetimeScope lifetimeScope) {
_contextDictonary = new Dictionary<string, object>();
@@ -30,8 +32,8 @@ namespace Orchard.Tests.Stubs {
CurrentSite = ci.As<ISite>();
_lifetimeScope = lifetimeScope;
if (InitMethod != null) {
InitMethod(this);
if (_initMethod != null) {
_initMethod(this);
}
}

View File

@@ -7,7 +7,7 @@ OrchardVersion: 1.0
Description: Description for the module
Features:
Orchard.MediaPicker:
Name: MediaPicker
Dependencies: Orchard.Media, Orchard.jQuery
Name: MediaPicker
Dependencies: Orchard.Media, Orchard.jQuery
Description: UI for browsing for, uploading, or selecting an image for an HTML editor.
Category: Input Editor
Category: Input Editor

View File

@@ -1,12 +1,46 @@
using System.Collections.Generic;
using Orchard.Environment.Extensions.Models;
using Orchard.Modules.Models;
namespace Orchard.Modules.Services {
public interface IModuleService : IDependency {
void EnableFeatures(IEnumerable<string> featureNames);
void EnableFeatures(IEnumerable<string> featureNames, bool force);
void DisableFeatures(IEnumerable<string> featureNames);
void DisableFeatures(IEnumerable<string> featureNames, bool force);
bool IsRecentlyInstalled(ExtensionDescriptor module);
/// <summary>
/// Retrieves an enumeration of the available features together with its state (enabled / disabled).
/// </summary>
/// <returns>An enumeration of the available features together with its state (enabled / disabled).</returns>
IEnumerable<ModuleFeature> GetAvailableFeatures();
/// <summary>
/// Enables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be enabled.</param>
void EnableFeatures(IEnumerable<string> featureIds);
/// <summary>
/// Enables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be enabled.</param>
/// <param name="force">Boolean parameter indicating if the feature should enable it's dependencies if required or fail otherwise.</param>
void EnableFeatures(IEnumerable<string> featureIds, bool force);
/// <summary>
/// Disables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be disabled.</param>
void DisableFeatures(IEnumerable<string> featureIds);
/// <summary>
/// Disables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be disabled.</param>
/// <param name="force">Boolean parameter indicating if the feature should disable the features which depend on it if required or fail otherwise.</param>
void DisableFeatures(IEnumerable<string> featureIds, bool force);
/// <summary>
/// Determines if an extension was recently installed.
/// </summary>
/// <param name="extensionDescriptor">The extension descriptor.</param>
/// <returns>True if the feature was recently installed; false otherwise.</returns>
bool IsRecentlyInstalled(ExtensionDescriptor extensionDescriptor);
}
}

View File

@@ -4,7 +4,7 @@ using System.Linq;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.Environment.Descriptor;
using Orchard.Environment.Descriptor.Models;
using Orchard.Environment.Features;
using Orchard.FileSystems.VirtualPath;
using Orchard.Localization;
using Orchard.Modules.Models;
@@ -12,28 +12,38 @@ using Orchard.UI.Notify;
namespace Orchard.Modules.Services {
public class ModuleService : IModuleService {
private readonly IFeatureManager _featureManager;
private readonly IVirtualPathProvider _virtualPathProvider;
private readonly IExtensionManager _extensionManager;
private readonly IShellDescriptorManager _shellDescriptorManager;
public ModuleService(
IFeatureManager featureManager,
IOrchardServices orchardServices,
IVirtualPathProvider virtualPathProvider,
IExtensionManager extensionManager,
IShellDescriptorManager shellDescriptorManager) {
Services = orchardServices;
_featureManager = featureManager;
_virtualPathProvider = virtualPathProvider;
_extensionManager = extensionManager;
_shellDescriptorManager = shellDescriptorManager;
if (_featureManager.FeatureDependencyNotification == null) {
_featureManager.FeatureDependencyNotification = GenerateWarning;
}
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public IOrchardServices Services { get; set; }
/// <summary>
/// Retrieves an enumeration of the available features together with its state (enabled / disabled).
/// </summary>
/// <returns>An enumeration of the available features together with its state (enabled / disabled).</returns>
public IEnumerable<ModuleFeature> GetAvailableFeatures() {
var enabledFeatures = _shellDescriptorManager.GetShellDescriptor().Features;
return _extensionManager.AvailableExtensions()
@@ -42,64 +52,50 @@ namespace Orchard.Modules.Services {
.FirstOrDefault(sf => string.Equals(sf.Name, f.Descriptor.Id, StringComparison.OrdinalIgnoreCase)) != null));
}
public void EnableFeatures(IEnumerable<string> featureNames) {
EnableFeatures(featureNames, false);
/// <summary>
/// Enables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be enabled.</param>
public void EnableFeatures(IEnumerable<string> featureIds) {
EnableFeatures(featureIds, false);
}
public void EnableFeatures(IEnumerable<string> features, bool force) {
var shellDescriptor = _shellDescriptorManager.GetShellDescriptor();
var enabledFeatures = shellDescriptor.Features.ToList();
var availableFeatures = GetAvailableFeatures().ToList();
var featuresToEnable = features
.Select(s => EnableFeature(s, availableFeatures, force)).ToList()
.SelectMany(ies => ies.Select(s => s));
if (featuresToEnable.Count() == 0)
return;
foreach (var featureToEnable in featuresToEnable) {
var feature = featureToEnable;
enabledFeatures.Add(new ShellFeature { Name = feature });
Services.Notifier.Information(T("{0} was enabled", featureToEnable));
/// <summary>
/// Enables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be enabled.</param>
/// <param name="force">Boolean parameter indicating if the feature should enable it's dependencies if required or fail otherwise.</param>
public void EnableFeatures(IEnumerable<string> featureIds, bool force) {
foreach (string featureId in _featureManager.EnableFeatures(featureIds, force)) {
Services.Notifier.Information(T("{0} was enabled", featureId));
}
_shellDescriptorManager.UpdateShellDescriptor(shellDescriptor.SerialNumber, enabledFeatures,
shellDescriptor.Parameters);
}
public void DisableFeatures(IEnumerable<string> featureNames) {
DisableFeatures(featureNames, false);
/// <summary>
/// Disables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be disabled.</param>
public void DisableFeatures(IEnumerable<string> featureIds) {
DisableFeatures(featureIds, false);
}
public void DisableFeatures(IEnumerable<string> features, bool force) {
var shellDescriptor = _shellDescriptorManager.GetShellDescriptor();
var enabledFeatures = shellDescriptor.Features.ToList();
var availableFeatures = GetAvailableFeatures().ToList();
var featuresToDisable = features
.Select(s => DisableFeature(s, availableFeatures, force)).ToList()
.SelectMany(ies => ies.Select(s => s));
if (featuresToDisable.Count() == 0)
return;
foreach (var featureToDisable in featuresToDisable) {
var feature = featureToDisable;
enabledFeatures.RemoveAll(f => f.Name == feature);
Services.Notifier.Information(T("{0} was disabled", feature));
/// <summary>
/// Disables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be disabled.</param>
/// <param name="force">Boolean parameter indicating if the feature should disable the features which depend on it if required or fail otherwise.</param>
public void DisableFeatures(IEnumerable<string> featureIds, bool force) {
foreach (string featureId in _featureManager.DisableFeatures(featureIds, force)) {
Services.Notifier.Information(T("{0} was disabled", featureId));
}
_shellDescriptorManager.UpdateShellDescriptor(shellDescriptor.SerialNumber, enabledFeatures,
shellDescriptor.Parameters);
}
/// <summary>
/// Determines if a module was recently installed by using the project's last written time.
/// </summary>
/// <param name="descriptor">The extension descriptor.</param>
public bool IsRecentlyInstalled(ExtensionDescriptor descriptor) {
string projectFile = GetManifestPath(descriptor);
/// <param name="extensionDescriptor">The extension descriptor.</param>
public bool IsRecentlyInstalled(ExtensionDescriptor extensionDescriptor) {
string projectFile = GetManifestPath(extensionDescriptor);
if (!string.IsNullOrEmpty(projectFile)) {
// If project file was modified less than 24 hours ago, the module was recently deployed
return DateTime.UtcNow.Subtract(_virtualPathProvider.GetFileLastWriteTimeUtc(projectFile)) < new TimeSpan(1, 0, 0, 0);
@@ -108,9 +104,13 @@ namespace Orchard.Modules.Services {
return false;
}
private string GetManifestPath(ExtensionDescriptor descriptor) {
string projectPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Id,
"module.txt");
/// <summary>
/// Retrieves the full path of the manifest file for a module's extension descriptor.
/// </summary>
/// <param name="extensionDescriptor">The module's extension descriptor.</param>
/// <returns>The full path to the module's manifest file.</returns>
private string GetManifestPath(ExtensionDescriptor extensionDescriptor) {
string projectPath = _virtualPathProvider.Combine(extensionDescriptor.Location, extensionDescriptor.Id, "module.txt");
if (!_virtualPathProvider.FileExists(projectPath)) {
return null;
@@ -119,81 +119,13 @@ namespace Orchard.Modules.Services {
return projectPath;
}
private IEnumerable<string> EnableFeature(string featureName, IEnumerable<ModuleFeature> features, bool force) {
var featuresList = features.ToList();
var getDisabledDependencies =
new Func<string, IEnumerable<ModuleFeature>, IEnumerable<ModuleFeature>>(
(n, fs) => {
var feature = fs.Single(f => f.Descriptor.Id == n);
return feature.Descriptor.Dependencies != null
? feature.Descriptor.Dependencies.Select(
fn => fs.Single(f => f.Descriptor.Id == fn)).Where(f => !f.IsEnabled)
: Enumerable.Empty<ModuleFeature>();
});
var featuresToEnable = GetAffectedFeatures(featureName, featuresList, getDisabledDependencies);
if (featuresToEnable.Count() > 1 && !force) {
GenerateWarning("If you want {0} enabled, then you'll also need to enable {1}.",
featureName,
featuresToEnable.Where(fn => fn != featureName));
return Enumerable.Empty<string>();
}
return featuresToEnable;
}
private IEnumerable<string> DisableFeature(string featureName, IEnumerable<ModuleFeature> features, bool force) {
var featuresList = features.ToList();
var getEnabledDependants =
new Func<string, IEnumerable<ModuleFeature>, IEnumerable<ModuleFeature>>(
(n, fs) => fs.Where(f => f.IsEnabled && f.Descriptor.Dependencies != null && f.Descriptor.Dependencies.Contains(n)));
var featuresToDisable = GetAffectedFeatures(featureName, featuresList, getEnabledDependants);
if (featuresToDisable.Count() > 1 && !force) {
GenerateWarning("If {0} is disabled, then you'll also lose {1}.",
featureName,
featuresToDisable.Where(fn => fn != featureName));
return Enumerable.Empty<string>();
}
return featuresToDisable;
}
private static IEnumerable<string> GetAffectedFeatures(string featureName, IEnumerable<ModuleFeature> features, Func<string, IEnumerable<ModuleFeature>, IEnumerable<ModuleFeature>> getAffectedDependencies) {
var dependencies = new List<string> {featureName};
foreach (var dependency in getAffectedDependencies(featureName, features))
dependencies.AddRange(GetAffectedFeatures(dependency.Descriptor.Id, features, getAffectedDependencies));
return dependencies;
}
private void GenerateWarning(string messageFormat, string featureName, IEnumerable<string> featuresInQuestion) {
if (featuresInQuestion.Count() < 1)
return;
Services.Notifier.Warning(T(
messageFormat,
featureName,
featuresInQuestion.Count() > 1
? string.Join("",
featuresInQuestion.Select(
(fn, i) =>
T(i == featuresInQuestion.Count() - 1
? "{0}"
: (i == featuresInQuestion.Count() - 2
? "{0} and "
: "{0}, "), fn).ToString()).ToArray())
: featuresInQuestion.First()));
}
private static ModuleFeature AssembleModuleFromDescriptor(Feature feature, bool isEnabled) {
return new ModuleFeature {
Descriptor = feature.Descriptor,
IsEnabled = isEnabled
};
}
private void GenerateWarning(string messageFormat, string featureName, IEnumerable<string> featuresInQuestion) {
if (featuresInQuestion.Count() < 1)
return;

View File

@@ -73,10 +73,6 @@
<Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project>
<Name>Orchard.Framework</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Modules\Orchard.Modules.csproj">
<Project>{17F86780-9A1F-4AA1-86F1-875EEC2730C7}</Project>
<Name>Orchard.Modules</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Packaging\Orchard.Packaging.csproj">
<Project>{DFD137A2-DDB5-4D22-BE0D-FA9AD4C8B059}</Project>
<Name>Orchard.Packaging</Name>

View File

@@ -4,18 +4,15 @@ using System.Linq;
using Orchard.Environment.Features;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Modules.Services;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
namespace Orchard.Recipes.RecipeHandlers {
public class FeatureRecipeHandler : IRecipeHandler {
private readonly IFeatureManager _featureManager;
private readonly IModuleService _moduleService;
public FeatureRecipeHandler(IFeatureManager featureManager, IModuleService moduleService) {
public FeatureRecipeHandler(IFeatureManager featureManager) {
_featureManager = featureManager;
_moduleService = moduleService;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
@@ -58,10 +55,10 @@ namespace Orchard.Recipes.RecipeHandlers {
}
if (featuresToDisable.Count != 0) {
_moduleService.DisableFeatures(featuresToDisable, true);
_featureManager.DisableFeatures(featuresToDisable, true);
}
if (featuresToEnable.Count != 0) {
_moduleService.EnableFeatures(featuresToEnable, true);
_featureManager.EnableFeatures(featuresToEnable, true);
}
recipeContext.Executed = true;

View File

@@ -4,9 +4,9 @@ using System.Web.Hosting;
using Orchard.Data.Migration;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.Environment.Features;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Modules.Services;
using Orchard.Packaging.Models;
using Orchard.Packaging.Services;
using Orchard.Recipes.Models;
@@ -17,20 +17,21 @@ namespace Orchard.Recipes.RecipeHandlers {
private readonly IPackagingSourceManager _packagingSourceManager;
private readonly IPackageManager _packageManager;
private readonly IExtensionManager _extensionManager;
private readonly IModuleService _moduleService;
private readonly IFeatureManager _featureManager;
private readonly IDataMigrationManager _dataMigrationManager;
public ModuleRecipeHandler(
IPackagingSourceManager packagingSourceManager,
IPackageManager packageManager,
IExtensionManager extensionManager,
IModuleService moduleService,
IFeatureManager featureManager,
IDataMigrationManager dataMigrationManager) {
_packagingSourceManager = packagingSourceManager;
_packageManager = packageManager;
_extensionManager = extensionManager;
_moduleService = moduleService;
_featureManager = featureManager;
_dataMigrationManager = dataMigrationManager;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
@@ -89,7 +90,7 @@ namespace Orchard.Recipes.RecipeHandlers {
from extensionDescriptor in extensions
where String.Equals(extensionDescriptor.Name, packagingEntry.Title, StringComparison.OrdinalIgnoreCase)
select extensionDescriptor.Features.Select(f => f.Name).ToArray()) {
_moduleService.EnableFeatures(features);
_featureManager.EnableFeatures(features);
_dataMigrationManager.Update(features);
installed = true;
}

View File

@@ -1,124 +1,202 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.Environment.Descriptor;
using Orchard.Environment.Descriptor.Models;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.Localization;
using Orchard.Logging;
namespace Orchard.Environment.Features {
public interface IFeatureManager : IDependency {
IEnumerable<FeatureDescriptor> GetAvailableFeatures();
IEnumerable<FeatureDescriptor> GetEnabledFeatures();
void EnableFeatures(IEnumerable<string> featureNames);
void DisableFeatures(IEnumerable<string> featureNames);
}
public class FeatureManager : IFeatureManager {
private readonly IExtensionManager _extensionManager;
private readonly ShellDescriptor _shellDescriptor;
private readonly IShellDescriptorManager _shellDescriptorManager;
/// <summary>
/// Delegate to notify about feature dependencies.
/// </summary>
public FeatureDependencyNotificationHandler FeatureDependencyNotification { get; set; }
public FeatureManager(
IExtensionManager extensionManager,
ShellDescriptor shellDescriptor,
IShellDescriptorManager shellDescriptorManager) {
_extensionManager = extensionManager;
_shellDescriptor = shellDescriptor;
_shellDescriptorManager = shellDescriptorManager;
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
}
public Localizer T { get; set; }
public ILogger Logger { get; set; }
/// <summary>
/// Retrieves the available features.
/// </summary>
/// <returns>An enumeration of feature descriptors for the available features.</returns>
public IEnumerable<FeatureDescriptor> GetAvailableFeatures() {
return _extensionManager.AvailableFeatures();
}
/// <summary>
/// Retrieves the enabled features.
/// </summary>
/// <returns>An enumeration of feature descriptors for the enabled features.</returns>
public IEnumerable<FeatureDescriptor> GetEnabledFeatures() {
var currentShellDescriptor = _shellDescriptorManager.GetShellDescriptor();
return _extensionManager.EnabledFeatures(currentShellDescriptor);
}
public void EnableFeatures(IEnumerable<string> featureNames) {
var currentShellDescriptor = _shellDescriptorManager.GetShellDescriptor();
var updatedFeatures = currentShellDescriptor.Features
.Union(featureNames
.Where(name => !currentShellDescriptor.Features.Any(sf => sf.Name == name))
.Select(name => new ShellFeature {Name = name}));
_shellDescriptorManager.UpdateShellDescriptor(
currentShellDescriptor.SerialNumber,
updatedFeatures,
currentShellDescriptor.Parameters);
/// <summary>
/// Enables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be enabled.</param>
public IEnumerable<string> EnableFeatures(IEnumerable<string> featureIds) {
return EnableFeatures(featureIds, false);
}
public void DisableFeatures(IEnumerable<string> featureNames) {
var currentShellDescriptor = _shellDescriptorManager.GetShellDescriptor();
/// <summary>
/// Enables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be enabled.</param>
/// <param name="force">Boolean parameter indicating if the feature should enable it's dependencies if required or fail otherwise.</param>
public IEnumerable<string> EnableFeatures(IEnumerable<string> featureIds, bool force) {
ShellDescriptor shellDescriptor = _shellDescriptorManager.GetShellDescriptor();
List<ShellFeature> enabledFeatures = shellDescriptor.Features.ToList();
var updatedFeatures = currentShellDescriptor.Features
.Where(sf => !featureNames.Contains(sf.Name));
IDictionary<FeatureDescriptor, bool> availableFeatures = GetAvailableFeatures()
.ToDictionary(featureDescriptor => featureDescriptor,
featureDescriptor => enabledFeatures.FirstOrDefault(shellFeature => shellFeature.Name == featureDescriptor.Id) != null);
_shellDescriptorManager.UpdateShellDescriptor(
currentShellDescriptor.SerialNumber,
updatedFeatures,
currentShellDescriptor.Parameters);
IEnumerable<string> featuresToEnable = featureIds
.Select(featureId => EnableFeature(featureId, availableFeatures, force)).ToList()
.SelectMany(ies => ies.Select(s => s));
if (featuresToEnable.Count() > 0) {
foreach (string featureId in featuresToEnable) {
string id = featureId;
enabledFeatures.Add(new ShellFeature { Name = id });
Logger.Information(T("{0} was enabled", featureId).ToString());
}
_shellDescriptorManager.UpdateShellDescriptor(shellDescriptor.SerialNumber, enabledFeatures,
shellDescriptor.Parameters);
}
return featuresToEnable;
}
/// <summary>
/// Disables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be disabled.</param>
/// <returns>An enumeration with the disabled feature IDs.</returns>
public IEnumerable<string> DisableFeatures(IEnumerable<string> featureIds) {
return DisableFeatures(featureIds, false);
}
//private void DisableThemeFeatures(string themeName) {
// var themes = new Queue<string>();
// while (themeName != null) {
// if (themes.Contains(themeName))
// throw new InvalidOperationException(T("The theme \"{0}\" is already in the stack of themes that need features disabled.", themeName).Text);
// var theme = GetThemeByName(themeName);
// if (theme == null)
// break;
// themes.Enqueue(themeName);
/// <summary>
/// Disables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be disabled.</param>
/// <param name="force">Boolean parameter indicating if the feature should disable the features which depend on it if required or fail otherwise.</param>
/// <returns>An enumeration with the disabled feature IDs.</returns>
public IEnumerable<string> DisableFeatures(IEnumerable<string> featureIds, bool force) {
ShellDescriptor shellDescriptor = _shellDescriptorManager.GetShellDescriptor();
List<ShellFeature> enabledFeatures = shellDescriptor.Features.ToList();
// themeName = !string.IsNullOrWhiteSpace(theme.BaseTheme)
// ? theme.BaseTheme
// : null;
IDictionary<FeatureDescriptor, bool> availableFeatures = GetAvailableFeatures()
.ToDictionary(featureDescriptor => featureDescriptor,
featureDescriptor => enabledFeatures.FirstOrDefault(shellFeature => shellFeature.Name == featureDescriptor.Id) != null);
// }
IEnumerable<string> featuresToDisable = featureIds
.Select(featureId => DisableFeature(featureId, availableFeatures, force)).ToList()
.SelectMany(ies => ies.Select(s => s));
// while (themes.Count > 0)
// _moduleService.DisableFeatures(new[] { themes.Dequeue() });
//}
if (featuresToDisable.Count() > 0) {
foreach (string featureId in featuresToDisable) {
string id = featureId;
//private void EnableThemeFeatures(string themeName) {
// var themes = new Stack<string>();
// while (themeName != null) {
// if (themes.Contains(themeName))
// throw new InvalidOperationException(T("The theme \"{0}\" is already in the stack of themes that need features enabled.", themeName).Text);
// themes.Push(themeName);
enabledFeatures.RemoveAll(shellFeature => shellFeature.Name == id);
Logger.Information(T("{0} was disabled", featureId).ToString());
}
// var theme = GetThemeByName(themeName);
// themeName = !string.IsNullOrWhiteSpace(theme.BaseTheme)
// ? theme.BaseTheme
// : null;
// }
_shellDescriptorManager.UpdateShellDescriptor(shellDescriptor.SerialNumber, enabledFeatures,
shellDescriptor.Parameters);
}
// while (themes.Count > 0)
// _moduleService.EnableFeatures(new[] { themes.Pop() });
//}
return featuresToDisable;
}
//private bool DoEnableTheme(string themeName) {
// if (string.IsNullOrWhiteSpace(themeName))
// return false;
/// <summary>
/// Enables a feature.
/// </summary>
/// <param name="featureId">The ID of the feature to be enabled.</param>
/// <param name="availableFeatures">A dictionary of the available feature descriptors and their current state (enabled / disabled).</param>
/// <param name="force">Boolean parameter indicating if the feature should enable it's dependencies if required or fail otherwise.</param>
/// <returns>An enumeration of the enabled features.</returns>
private IEnumerable<string> EnableFeature(string featureId, IDictionary<FeatureDescriptor, bool> availableFeatures, bool force) {
var getDisabledDependencies =
new Func<string, IDictionary<FeatureDescriptor, bool>, IDictionary<FeatureDescriptor, bool>>(
(currentFeatureId, featuresState) => {
KeyValuePair<FeatureDescriptor, bool> feature = featuresState.Single(featureState => featureState.Key.Id == currentFeatureId);
// //todo: (heskew) need messages given in addition to all of these early returns so something meaningful can be presented to the user
// var themeToEnable = GetThemeByName(themeName);
// if (themeToEnable == null)
// return false;
// Retrieve disabled dependencies for the current feature
return feature.Key.Dependencies
.Select(fId => featuresState.Single(featureState => featureState.Key.Id == fId))
.Where(featureState => !featureState.Value)
.ToDictionary(f => f.Key, f => f.Value);
});
// // ensure all base themes down the line are present and accounted for
// //todo: (heskew) dito on the need of a meaningful message
// if (!AllBaseThemesAreInstalled(themeToEnable.BaseTheme))
// return false;
IEnumerable<string> featuresToEnable = GetAffectedFeatures(featureId, availableFeatures, getDisabledDependencies);
if (featuresToEnable.Count() > 1 && !force) {
Logger.Warning(T("Aditional features need to be enabled.").ToString());
if (FeatureDependencyNotification != null) {
FeatureDependencyNotification("If {0} is enabled, then you'll also need to enable {1}.", featureId, featuresToEnable.Where(fId => fId != featureId));
}
// // enable all theme features
// EnableThemeFeatures(themeToEnable.Name);
// return true;
//}
return Enumerable.Empty<string>();
}
return featuresToEnable;
}
/// <summary>
/// Disables a feature.
/// </summary>
/// <param name="featureId">The ID of the feature to be enabled.</param>
/// <param name="availableFeatures"></param>
/// <param name="force">Boolean parameter indicating if the feature should enable it's dependencies if required or fail otherwise.</param>
/// <returns>An enumeration of the disabled features.</returns>
private IEnumerable<string> DisableFeature(string featureId, IDictionary<FeatureDescriptor, bool> availableFeatures, bool force) {
var getEnabledDependants =
new Func<string, IDictionary<FeatureDescriptor, bool>, IDictionary<FeatureDescriptor, bool>>(
(currentFeatureId, fs) => fs.Where(f => f.Value && f.Key.Dependencies != null && f.Key.Dependencies.Contains(currentFeatureId))
.ToDictionary(f => f.Key, f => f.Value));
IEnumerable<string> featuresToDisable = GetAffectedFeatures(featureId, availableFeatures, getEnabledDependants);
if (featuresToDisable.Count() > 1 && !force) {
Logger.Warning(T("Aditional features need to be disabled.").ToString());
if (FeatureDependencyNotification != null) {
FeatureDependencyNotification("If {0} is disabled, then you'll also need to disable {1}.", featureId, featuresToDisable.Where(fId => fId != featureId));
}
return Enumerable.Empty<string>();
}
return featuresToDisable;
}
private static IEnumerable<string> GetAffectedFeatures(string featureId, IDictionary<FeatureDescriptor, bool> features, Func<string, IDictionary<FeatureDescriptor, bool>, IDictionary<FeatureDescriptor, bool>> getAffectedDependencies) {
var dependencies = new List<string> { featureId };
foreach (KeyValuePair<FeatureDescriptor, bool> dependency in getAffectedDependencies(featureId, features)) {
dependencies.AddRange(GetAffectedFeatures(dependency.Key.Id, features, getAffectedDependencies));
}
return dependencies;
}
}
}

View File

@@ -0,0 +1,52 @@
using System.Collections.Generic;
using Orchard.Environment.Extensions.Models;
namespace Orchard.Environment.Features {
public delegate void FeatureDependencyNotificationHandler(string messageFormat, string featureId, IEnumerable<string> featureIds);
public interface IFeatureManager : IDependency {
FeatureDependencyNotificationHandler FeatureDependencyNotification { get; set; }
/// <summary>
/// Retrieves the available features.
/// </summary>
/// <returns>An enumeration of feature descriptors for the available features.</returns>
IEnumerable<FeatureDescriptor> GetAvailableFeatures();
/// <summary>
/// Retrieves the enabled features.
/// </summary>
/// <returns>An enumeration of feature descriptors for the enabled features.</returns>
IEnumerable<FeatureDescriptor> GetEnabledFeatures();
/// <summary>
/// Enables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be enabled.</param>
/// <returns>An enumeration with the enabled feature IDs.</returns>
IEnumerable<string> EnableFeatures(IEnumerable<string> featureIds);
/// <summary>
/// Enables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be enabled.</param>
/// <param name="force">Boolean parameter indicating if the feature should enable it's dependencies if required or fail otherwise.</param>
/// <returns>An enumeration with the enabled feature IDs.</returns>
IEnumerable<string> EnableFeatures(IEnumerable<string> featureIds, bool force);
/// <summary>
/// Disables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be disabled.</param>
/// <returns>An enumeration with the disabled feature IDs.</returns>
IEnumerable<string> DisableFeatures(IEnumerable<string> featureIds);
/// <summary>
/// Disables a list of features.
/// </summary>
/// <param name="featureIds">The IDs for the features to be disabled.</param>
/// <param name="force">Boolean parameter indicating if the feature should disable the features which depend on it if required or fail otherwise.</param>
/// <returns>An enumeration with the disabled feature IDs.</returns>
IEnumerable<string> DisableFeatures(IEnumerable<string> featureIds, bool force);
}
}

View File

@@ -3,7 +3,6 @@ using Orchard.Localization;
namespace Orchard.Localization {
public delegate LocalizedString Localizer(string text, params object[] args);
}
namespace Orchard.Mvc.Html {

View File

@@ -173,6 +173,7 @@
<Compile Include="DisplayManagement\Implementation\IShapeDisplayEvents.cs" />
<Compile Include="DisplayManagement\Implementation\IShapeFactoryEvents.cs" />
<Compile Include="DisplayManagement\Shapes\ITagBuilderFactory.cs" />
<Compile Include="Environment\Features\IFeatureManager.cs" />
<Compile Include="Environment\IAssemblyNameResolver.cs" />
<Compile Include="Environment\Extensions\Models\DefaultExtensionTypes.cs" />
<Compile Include="Environment\HostEnvironment.cs" />