mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-12-02 11:44:41 +08:00
Implementing feature priorities
--HG-- branch : dev
This commit is contained in:
@@ -506,9 +506,7 @@ OrchardVersion: 1
|
|||||||
Name: Alpha
|
Name: Alpha
|
||||||
Version: 1.0.3
|
Version: 1.0.3
|
||||||
OrchardVersion: 1
|
OrchardVersion: 1
|
||||||
Features:
|
Dependencies: Gamma
|
||||||
Alpha:
|
|
||||||
Dependencies: Gamma
|
|
||||||
");
|
");
|
||||||
|
|
||||||
moduleExtensionFolder.Manifests.Add("Beta", @"
|
moduleExtensionFolder.Manifests.Add("Beta", @"
|
||||||
@@ -520,23 +518,139 @@ OrchardVersion: 1
|
|||||||
Name: Gamma
|
Name: Gamma
|
||||||
Version: 1.0.3
|
Version: 1.0.3
|
||||||
OrchardVersion: 1
|
OrchardVersion: 1
|
||||||
Features:
|
Dependencies: Beta
|
||||||
Gamma:
|
|
||||||
Dependencies: Beta
|
|
||||||
");
|
");
|
||||||
|
|
||||||
moduleExtensionFolder.Manifests.Add("Classic", @"
|
moduleExtensionFolder.Manifests.Add("Classic", @"
|
||||||
Name: Classic
|
Name: Classic
|
||||||
Version: 1.0.3
|
Version: 1.0.3
|
||||||
OrchardVersion: 1
|
OrchardVersion: 1
|
||||||
Features:
|
Dependencies: Alpha
|
||||||
Classic:
|
|
||||||
Dependencies: Alpha
|
|
||||||
");
|
");
|
||||||
|
|
||||||
IExtensionManager extensionManager = new ExtensionManager(new[] { moduleExtensionFolder, themeExtensionFolder }, new[] { extensionLoader }, new StubCacheManager());
|
IExtensionManager extensionManager = new ExtensionManager(new[] { moduleExtensionFolder, themeExtensionFolder }, new[] { extensionLoader }, new StubCacheManager());
|
||||||
var features = extensionManager.AvailableFeatures();
|
var features = extensionManager.AvailableFeatures();
|
||||||
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Gamma<Alpha<Classic<"));
|
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Gamma<Alpha<Classic<"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void FeatureDescriptorsAreInDependencyAndPriorityOrder() {
|
||||||
|
var extensionLoader = new StubLoaders();
|
||||||
|
var extensionFolder = new StubFolders();
|
||||||
|
|
||||||
|
// Check that priorities apply correctly on items on the same level of dependencies and are overwritten by dependencies
|
||||||
|
extensionFolder.Manifests.Add("Alpha", @"
|
||||||
|
Name: Alpha
|
||||||
|
Version: 1.0.3
|
||||||
|
OrchardVersion: 1
|
||||||
|
Dependencies: Gamma
|
||||||
|
Priority:2"); // More important than Gamma but will get overwritten by the dependency
|
||||||
|
|
||||||
|
extensionFolder.Manifests.Add("Beta", @"
|
||||||
|
Name: Beta
|
||||||
|
Version: 1.0.3
|
||||||
|
OrchardVersion: 1
|
||||||
|
Priority:2");
|
||||||
|
|
||||||
|
extensionFolder.Manifests.Add("Foo", @"
|
||||||
|
Name: Foo
|
||||||
|
Version: 1.0.3
|
||||||
|
OrchardVersion: 1
|
||||||
|
Priority:1");
|
||||||
|
|
||||||
|
extensionFolder.Manifests.Add("Gamma", @"
|
||||||
|
Name: Gamma
|
||||||
|
Version: 1.0.3
|
||||||
|
OrchardVersion: 1
|
||||||
|
Dependencies: Beta, Foo
|
||||||
|
Priority:3");
|
||||||
|
|
||||||
|
IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
|
||||||
|
var features = extensionManager.AvailableFeatures();
|
||||||
|
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Foo<Beta<Gamma<Alpha<"));
|
||||||
|
|
||||||
|
// Change priorities and see that it reflects properly
|
||||||
|
extensionFolder.Manifests["Foo"] = @"
|
||||||
|
Name: Foo
|
||||||
|
Version: 1.0.3
|
||||||
|
OrchardVersion: 1
|
||||||
|
Priority:3";
|
||||||
|
|
||||||
|
extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
|
||||||
|
features = extensionManager.AvailableFeatures();
|
||||||
|
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Foo<Gamma<Alpha<"));
|
||||||
|
|
||||||
|
// Remove dependency on Beta and see that it moves down the list since no one depends on it anymore
|
||||||
|
extensionFolder.Manifests["Gamma"] = @"
|
||||||
|
Name: Gamma
|
||||||
|
Version: 1.0.3
|
||||||
|
OrchardVersion: 1
|
||||||
|
Dependencies: Beta
|
||||||
|
Priority:3";
|
||||||
|
|
||||||
|
extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
|
||||||
|
features = extensionManager.AvailableFeatures();
|
||||||
|
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Foo<Gamma<Alpha<"));
|
||||||
|
|
||||||
|
// Change Foo to depend on Gamma and see that it moves to a new position (same dependencies as alpha but lower priority)
|
||||||
|
extensionFolder.Manifests["Foo"] = @"
|
||||||
|
Name: Foo
|
||||||
|
Version: 1.0.3
|
||||||
|
OrchardVersion: 1
|
||||||
|
Priority:3
|
||||||
|
Dependencies: Gamma";
|
||||||
|
|
||||||
|
extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
|
||||||
|
features = extensionManager.AvailableFeatures();
|
||||||
|
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Gamma<Alpha<Foo<"));
|
||||||
|
|
||||||
|
// Update Foo to a higher priority than alpha
|
||||||
|
extensionFolder.Manifests["Foo"] = @"
|
||||||
|
Name: Foo
|
||||||
|
Version: 1.0.3
|
||||||
|
OrchardVersion: 1
|
||||||
|
Priority:1
|
||||||
|
Dependencies: Gamma";
|
||||||
|
|
||||||
|
extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
|
||||||
|
features = extensionManager.AvailableFeatures();
|
||||||
|
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Gamma<Foo<Alpha<"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void FeatureDescriptorsAreInPriorityOrder() {
|
||||||
|
var extensionLoader = new StubLoaders();
|
||||||
|
var extensionFolder = new StubFolders();
|
||||||
|
|
||||||
|
// Check that priorities apply correctly on items on the same level of dependencies and are overwritten by dependencies
|
||||||
|
extensionFolder.Manifests.Add("Alpha", @"
|
||||||
|
Name: Alpha
|
||||||
|
Version: 1.0.3
|
||||||
|
OrchardVersion: 1
|
||||||
|
Priority:4"); // More important than Gamma but will get overwritten by the dependency
|
||||||
|
|
||||||
|
extensionFolder.Manifests.Add("Beta", @"
|
||||||
|
Name: Beta
|
||||||
|
Version: 1.0.3
|
||||||
|
OrchardVersion: 1
|
||||||
|
Priority:3");
|
||||||
|
|
||||||
|
extensionFolder.Manifests.Add("Foo", @"
|
||||||
|
Name: Foo
|
||||||
|
Version: 1.0.3
|
||||||
|
OrchardVersion: 1
|
||||||
|
Priority:1");
|
||||||
|
|
||||||
|
extensionFolder.Manifests.Add("Gamma", @"
|
||||||
|
Name: Gamma
|
||||||
|
Version: 1.0.3
|
||||||
|
OrchardVersion: 1
|
||||||
|
Priority:2");
|
||||||
|
|
||||||
|
IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
|
||||||
|
var features = extensionManager.AvailableFeatures();
|
||||||
|
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Foo<Gamma<Beta<Alpha<"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ namespace Orchard.DisplayManagement.Descriptors {
|
|||||||
|
|
||||||
var alterations = builderFactory.BuildAlterations()
|
var alterations = builderFactory.BuildAlterations()
|
||||||
.Where(alteration => IsModuleOrRequestedTheme(alteration, themeName))
|
.Where(alteration => IsModuleOrRequestedTheme(alteration, themeName))
|
||||||
.OrderByDependencies(AlterationHasDependency);
|
.OrderByDependenciesAndPriorities(AlterationHasDependency, GetPriority);
|
||||||
|
|
||||||
var descriptors = alterations.GroupBy(alteration => alteration.ShapeType, StringComparer.OrdinalIgnoreCase)
|
var descriptors = alterations.GroupBy(alteration => alteration.ShapeType, StringComparer.OrdinalIgnoreCase)
|
||||||
.Select(group => group.Aggregate(
|
.Select(group => group.Aggregate(
|
||||||
@@ -54,6 +54,10 @@ namespace Orchard.DisplayManagement.Descriptors {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int GetPriority(ShapeAlteration shapeAlteration) {
|
||||||
|
return shapeAlteration.Feature.Descriptor.Priority;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool AlterationHasDependency(ShapeAlteration item, ShapeAlteration subject) {
|
private static bool AlterationHasDependency(ShapeAlteration item, ShapeAlteration subject) {
|
||||||
return ExtensionManager.HasDependency(item.Feature.Descriptor, subject.Feature.Descriptor);
|
return ExtensionManager.HasDependency(item.Feature.Descriptor, subject.Feature.Descriptor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,9 +199,10 @@ namespace Orchard.Environment.Extensions {
|
|||||||
.ToDictionary(g => g.Key, g => g.AsEnumerable(), StringComparer.OrdinalIgnoreCase);
|
.ToDictionary(g => g.Key, g => g.AsEnumerable(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var sortedAvailableExtensions =
|
var sortedAvailableExtensions =
|
||||||
availableExtensions.OrderByDependencies(
|
availableExtensions.OrderByDependenciesAndPriorities(
|
||||||
(item, dep) => referencesByModule.ContainsKey(item.Id) &&
|
(item, dep) => referencesByModule.ContainsKey(item.Id) &&
|
||||||
referencesByModule[item.Id].Any(r => StringComparer.OrdinalIgnoreCase.Equals(dep.Id, r.Name)))
|
referencesByModule[item.Id].Any(r => StringComparer.OrdinalIgnoreCase.Equals(dep.Id, r.Name)),
|
||||||
|
(item) => item.Priority)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
return new ExtensionLoadingContext {
|
return new ExtensionLoadingContext {
|
||||||
|
|||||||
@@ -39,7 +39,11 @@ namespace Orchard.Environment.Extensions {
|
|||||||
|
|
||||||
public IEnumerable<FeatureDescriptor> AvailableFeatures() {
|
public IEnumerable<FeatureDescriptor> AvailableFeatures() {
|
||||||
return _cacheManager.Get("...", ctx =>
|
return _cacheManager.Get("...", ctx =>
|
||||||
AvailableExtensions().SelectMany(ext => ext.Features).OrderByDependencies(HasDependency).ToReadOnlyCollection());
|
AvailableExtensions().SelectMany(ext => ext.Features).OrderByDependenciesAndPriorities(HasDependency, GetPriority).ToReadOnlyCollection());
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static int GetPriority(FeatureDescriptor featureDescriptor) {
|
||||||
|
return featureDescriptor.Priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ namespace Orchard.Environment.Extensions.Folders {
|
|||||||
AntiForgery = GetValue(manifest, "AntiForgery"),
|
AntiForgery = GetValue(manifest, "AntiForgery"),
|
||||||
Zones = GetValue(manifest, "Zones"),
|
Zones = GetValue(manifest, "Zones"),
|
||||||
BaseTheme = GetValue(manifest, "BaseTheme"),
|
BaseTheme = GetValue(manifest, "BaseTheme"),
|
||||||
|
Priority = int.Parse(GetValue(manifest, "Priority") ?? "0")
|
||||||
};
|
};
|
||||||
extensionDescriptor.Features = GetFeaturesForExtension(manifest, extensionDescriptor);
|
extensionDescriptor.Features = GetFeaturesForExtension(manifest, extensionDescriptor);
|
||||||
|
|
||||||
@@ -183,6 +184,9 @@ namespace Orchard.Environment.Extensions.Folders {
|
|||||||
case "FeatureDescription":
|
case "FeatureDescription":
|
||||||
manifest.Add("FeatureDescription", field[1]);
|
manifest.Add("FeatureDescription", field[1]);
|
||||||
break;
|
break;
|
||||||
|
case "Priority":
|
||||||
|
manifest.Add("Priority", field[1]);
|
||||||
|
break;
|
||||||
case "Features":
|
case "Features":
|
||||||
manifest.Add("Features", reader.ReadToEnd());
|
manifest.Add("Features", reader.ReadToEnd());
|
||||||
break;
|
break;
|
||||||
@@ -200,6 +204,7 @@ namespace Orchard.Environment.Extensions.Folders {
|
|||||||
FeatureDescriptor defaultFeature = new FeatureDescriptor {
|
FeatureDescriptor defaultFeature = new FeatureDescriptor {
|
||||||
Id = extensionDescriptor.Id,
|
Id = extensionDescriptor.Id,
|
||||||
Name = extensionDescriptor.Name,
|
Name = extensionDescriptor.Name,
|
||||||
|
Priority = extensionDescriptor.Priority,
|
||||||
Description = GetValue(manifest, "FeatureDescription") ?? GetValue(manifest, "Description") ?? string.Empty,
|
Description = GetValue(manifest, "FeatureDescription") ?? GetValue(manifest, "Description") ?? string.Empty,
|
||||||
Dependencies = ParseFeatureDependenciesEntry(GetValue(manifest, "Dependencies")),
|
Dependencies = ParseFeatureDependenciesEntry(GetValue(manifest, "Dependencies")),
|
||||||
Extension = extensionDescriptor,
|
Extension = extensionDescriptor,
|
||||||
@@ -229,6 +234,7 @@ namespace Orchard.Environment.Extensions.Folders {
|
|||||||
if (featureDescriptorId == extensionDescriptor.Id) {
|
if (featureDescriptorId == extensionDescriptor.Id) {
|
||||||
featureDescriptor = defaultFeature;
|
featureDescriptor = defaultFeature;
|
||||||
featureDescriptor.Name = extensionDescriptor.Name;
|
featureDescriptor.Name = extensionDescriptor.Name;
|
||||||
|
featureDescriptor.Priority = extensionDescriptor.Priority;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
featureDescriptor = new FeatureDescriptor {
|
featureDescriptor = new FeatureDescriptor {
|
||||||
@@ -256,6 +262,9 @@ namespace Orchard.Environment.Extensions.Folders {
|
|||||||
case "Category":
|
case "Category":
|
||||||
featureDescriptor.Category = featureField[1];
|
featureDescriptor.Category = featureField[1];
|
||||||
break;
|
break;
|
||||||
|
case "Priority":
|
||||||
|
featureDescriptor.Priority = int.Parse(featureField[1]);
|
||||||
|
break;
|
||||||
case "Dependencies":
|
case "Dependencies":
|
||||||
featureDescriptor.Dependencies = ParseFeatureDependenciesEntry(featureField[1]);
|
featureDescriptor.Dependencies = ParseFeatureDependenciesEntry(featureField[1]);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ namespace Orchard.Environment.Extensions.Models {
|
|||||||
public string AntiForgery { get; set; }
|
public string AntiForgery { get; set; }
|
||||||
public string Zones { get; set; }
|
public string Zones { get; set; }
|
||||||
public string BaseTheme { get; set; }
|
public string BaseTheme { get; set; }
|
||||||
|
public int Priority { get; set; }
|
||||||
|
|
||||||
public IEnumerable<FeatureDescriptor> Features { get; set; }
|
public IEnumerable<FeatureDescriptor> Features { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ namespace Orchard.Environment.Extensions.Models {
|
|||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Category { get; set; }
|
public string Category { get; set; }
|
||||||
|
public int Priority { get; set; }
|
||||||
public IEnumerable<string> Dependencies { get; set; }
|
public IEnumerable<string> Dependencies { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ namespace Orchard.Environment.ShellBuilders.Models {
|
|||||||
|
|
||||||
public class ShellBlueprintItem {
|
public class ShellBlueprintItem {
|
||||||
public Type Type { get; set; }
|
public Type Type { get; set; }
|
||||||
public Feature Feature { get; set; }
|
public Feature Feature { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DependencyBlueprint : ShellBlueprintItem {
|
public class DependencyBlueprint : ShellBlueprintItem {
|
||||||
|
|||||||
@@ -15,27 +15,82 @@ namespace Orchard.Utility {
|
|||||||
/// is a dependency of another, this algorithm will return the collection of elements sorted
|
/// is a dependency of another, this algorithm will return the collection of elements sorted
|
||||||
/// so that a given element in the sequence doesn't depend on any other element further in the sequence.
|
/// so that a given element in the sequence doesn't depend on any other element further in the sequence.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IEnumerable<T> OrderByDependencies<T>(this IEnumerable<T> elements, Func<T, T, bool> hasDependency) {
|
public static IEnumerable<T> OrderByDependenciesAndPriorities<T>(this IEnumerable<T> elements, Func<T, T, bool> hasDependency, Func<T, int> getPriority) {
|
||||||
var population = elements.Select(d => new Linkage<T> {
|
var population = elements.Select(d => new Linkage<T> {
|
||||||
Element = d
|
Element = d
|
||||||
}).ToArray();
|
}).OrderBy(item => getPriority(item.Element)).ToArray(); // Performing an initial sorting by priorities may optimize performance
|
||||||
|
|
||||||
var result = new List<T>();
|
var result = new List<T>();
|
||||||
foreach (var item in population) {
|
foreach (var item in population) {
|
||||||
Add(item, result, population, hasDependency);
|
Add(item, result, population, hasDependency, getPriority);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shift elements forward as possible within priorities and dependencies
|
||||||
|
for (int i = 1; i < result.Count; i++) {
|
||||||
|
int bestPosition = BestPriorityPosition(result, i, hasDependency, getPriority);
|
||||||
|
SwitchAndShift(result, i, bestPosition);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Add<T>(Linkage<T> item, ICollection<T> list, IEnumerable<Linkage<T>> population, Func<T, T, bool> hasDependency) {
|
private static void Add<T>(Linkage<T> item, ICollection<T> list, IEnumerable<Linkage<T>> population, Func<T, T, bool> hasDependency, Func<T, int> getPriority) {
|
||||||
if (item.Used)
|
if (item.Used)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
item.Used = true;
|
item.Used = true;
|
||||||
|
|
||||||
foreach (var dependency in population.Where(dep => hasDependency(item.Element, dep.Element))) {
|
foreach (var dependency in population.Where(dep => hasDependency(item.Element, dep.Element))) {
|
||||||
Add(dependency, list, population, hasDependency);
|
Add(dependency, list, population, hasDependency, getPriority);
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Add(item.Element);
|
list.Add(item.Element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int BestPriorityPosition<T>(List<T> list, int index, Func<T, T, bool> hasDependency, Func<T, int> getPriority) {
|
||||||
|
int bestPriority = getPriority(list[index]);
|
||||||
|
int bestIndex = index;
|
||||||
|
|
||||||
|
for (int i = index - 1 ; i >= 0 ; i--) {
|
||||||
|
if (hasDependency(list[index], list[i])) {
|
||||||
|
return bestIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
int currentPriority = getPriority(list[i]);
|
||||||
|
if (currentPriority >= bestPriority) {
|
||||||
|
bestIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Advances an element within the list from an initial position to a final position with a lower index.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of each element.</typeparam>
|
||||||
|
/// <param name="list">the list of elements.</param>
|
||||||
|
/// <param name="initialPosition">The initial position within the list.</param>
|
||||||
|
/// <param name="finalPosition">The final position within the list.</param>
|
||||||
|
/// <returns>True if any change was made; false otherwise.</returns>
|
||||||
|
private static bool SwitchAndShift<T>(List<T> list, int initialPosition, int finalPosition) {
|
||||||
|
if (initialPosition < finalPosition) {
|
||||||
|
throw new ArgumentException("finalPosition");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialPosition != finalPosition) {
|
||||||
|
T temp = list[initialPosition];
|
||||||
|
|
||||||
|
for (; initialPosition > finalPosition; initialPosition--) {
|
||||||
|
list[initialPosition] = list[initialPosition - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
list[finalPosition] = temp;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user