mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-01-23 21:32:14 +08:00
Finishing the implementation of recipe execution
StepQueue is now based off an IAppDataFolder --HG-- branch : recipe
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
@@ -8,9 +9,11 @@ using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Environment.Extensions.Folders;
|
||||
using Orchard.Environment.Extensions.Loaders;
|
||||
using Orchard.FileSystems.AppData;
|
||||
using Orchard.FileSystems.WebSite;
|
||||
using Orchard.Recipes.Models;
|
||||
using Orchard.Recipes.Services;
|
||||
using Orchard.Services;
|
||||
using Orchard.Tests.Stubs;
|
||||
|
||||
namespace Orchard.Tests.Modules.Recipes.Services {
|
||||
@@ -64,9 +67,11 @@ namespace Orchard.Tests.Modules.Recipes.Services {
|
||||
builder.RegisterType<RecipeManager>().As<IRecipeManager>();
|
||||
builder.RegisterType<RecipeHarvester>().As<IRecipeHarvester>();
|
||||
builder.RegisterType<RecipeStepExecutor>().As<IRecipeStepExecutor>();
|
||||
builder.RegisterType<RecipeStepQueue>().As<IRecipeStepQueue>().InstancePerLifetimeScope();
|
||||
builder.RegisterType<StubStepQueue>().As<IRecipeStepQueue>().InstancePerLifetimeScope();
|
||||
builder.RegisterType<StubRecipeScheduler>().As<IRecipeScheduler>();
|
||||
builder.RegisterType<ExtensionManager>().As<IExtensionManager>();
|
||||
builder.RegisterType<StubAppDataFolder>().As<IAppDataFolder>();
|
||||
builder.RegisterType<StubClock>().As<IClock>();
|
||||
builder.RegisterType<StubCacheManager>().As<ICacheManager>();
|
||||
builder.RegisterInstance(_folders).As<IExtensionFolders>();
|
||||
builder.RegisterType<Environment.Extensions.ExtensionManagerTests.StubLoaders>().As<IExtensionLoader>();
|
||||
@@ -140,6 +145,18 @@ namespace Orchard.Tests.Modules.Recipes.Services {
|
||||
}
|
||||
}
|
||||
|
||||
public class StubStepQueue : IRecipeStepQueue {
|
||||
readonly Queue<RecipeStep> _queue = new Queue<RecipeStep>();
|
||||
|
||||
public void Enqueue(string executionId, RecipeStep step) {
|
||||
_queue.Enqueue(step);
|
||||
}
|
||||
|
||||
public RecipeStep Dequeue(string executionId) {
|
||||
return _queue.Count == 0 ? null : _queue.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
public class StubRecipeScheduler : IRecipeScheduler {
|
||||
private readonly IRecipeStepExecutor _recipeStepExecutor;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Recipes.Models;
|
||||
@@ -20,14 +21,22 @@ namespace Orchard.Recipes.Services {
|
||||
ILogger Logger { get; set; }
|
||||
|
||||
public bool ExecuteNextStep(string executionId) {
|
||||
var recipeStepWorkItem = _recipeStepQueue.Dequeue(executionId);
|
||||
if (recipeStepWorkItem == null) {
|
||||
var nextRecipeStep= _recipeStepQueue.Dequeue(executionId);
|
||||
if (nextRecipeStep == null) {
|
||||
return false;
|
||||
}
|
||||
var recipeContext = new RecipeContext {RecipeStep = recipeStepWorkItem.Item1, Executed = false};
|
||||
foreach (var recipeHandler in _recipeHandlers) {
|
||||
recipeHandler.ExecuteRecipeStep(recipeContext);
|
||||
var recipeContext = new RecipeContext { RecipeStep = nextRecipeStep, Executed = false };
|
||||
try {
|
||||
foreach (var recipeHandler in _recipeHandlers) {
|
||||
recipeHandler.ExecuteRecipeStep(recipeContext);
|
||||
}
|
||||
}
|
||||
catch(Exception exception) {
|
||||
Logger.Error(exception, "Recipe execution {0} was cancelled because a step failed to execute", executionId);
|
||||
while (_recipeStepQueue.Dequeue(executionId) != null) ;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!recipeContext.Executed) {
|
||||
Logger.Error("Could not execute recipe step '{0}' because the recipe handler was not found.", recipeContext.RecipeStep.Name);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml.Linq;
|
||||
using Orchard.FileSystems.AppData;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Recipes.Models;
|
||||
|
||||
namespace Orchard.Recipes.Services {
|
||||
public class RecipeStepQueue : IRecipeStepQueue {
|
||||
// placeholder to be dropped in favor of an IAppDataFolder based impl
|
||||
private readonly Queue<Tuple<RecipeStep, string>> _stepQueue;
|
||||
private readonly IAppDataFolder _appDataFolder;
|
||||
private readonly string _recipeQueueFolder = "RecipeQueue" + Path.DirectorySeparatorChar;
|
||||
|
||||
public RecipeStepQueue() {
|
||||
_stepQueue = new Queue<Tuple<RecipeStep, string>>();
|
||||
public RecipeStepQueue(IAppDataFolder appDataFolder) {
|
||||
_appDataFolder = appDataFolder;
|
||||
Logger = NullLogger.Instance;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
@@ -19,11 +22,73 @@ namespace Orchard.Recipes.Services {
|
||||
ILogger Logger { get; set; }
|
||||
|
||||
public void Enqueue(string executionId, RecipeStep step) {
|
||||
_stepQueue.Enqueue(new Tuple<RecipeStep, string>(step, executionId));
|
||||
var recipeStepElement = new XElement("RecipeStep");
|
||||
recipeStepElement.Add(new XElement("Name", step.Name));
|
||||
recipeStepElement.Add(step.Step);
|
||||
|
||||
if (_appDataFolder.DirectoryExists(Path.Combine(_recipeQueueFolder, executionId))) {
|
||||
int stepIndex = GetLastStepIndex(executionId) + 1;
|
||||
_appDataFolder.CreateFile(Path.Combine(_recipeQueueFolder, executionId + Path.DirectorySeparatorChar + stepIndex),
|
||||
recipeStepElement.ToString());
|
||||
}
|
||||
else {
|
||||
_appDataFolder.CreateFile(
|
||||
Path.Combine(_recipeQueueFolder, executionId + Path.DirectorySeparatorChar + "0"),
|
||||
recipeStepElement.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public Tuple<RecipeStep, string> Dequeue(string executionId) {
|
||||
return _stepQueue.Count > 0 ? _stepQueue.Dequeue() : null;
|
||||
public RecipeStep Dequeue(string executionId) {
|
||||
if (!_appDataFolder.DirectoryExists(Path.Combine(_recipeQueueFolder, executionId))) {
|
||||
return null;
|
||||
}
|
||||
RecipeStep recipeStep = null;
|
||||
int stepIndex = GetFirstStepIndex(executionId);
|
||||
if (stepIndex >= 0) {
|
||||
var stepPath = Path.Combine(_recipeQueueFolder, executionId + Path.DirectorySeparatorChar + stepIndex);
|
||||
// string to xelement
|
||||
var stepElement = XElement.Parse(_appDataFolder.ReadFile(stepPath));
|
||||
var stepName = stepElement.Element("Name").Value;
|
||||
recipeStep = new RecipeStep {
|
||||
Name = stepName,
|
||||
Step = stepElement.Element(stepName)
|
||||
};
|
||||
_appDataFolder.DeleteFile(stepPath);
|
||||
}
|
||||
|
||||
if (stepIndex < 1) {
|
||||
_appDataFolder.DeleteFile(Path.Combine(_recipeQueueFolder, executionId));
|
||||
}
|
||||
|
||||
return recipeStep;
|
||||
}
|
||||
|
||||
private int GetFirstStepIndex(string executionId) {
|
||||
var stepFiles = new List<string>(_appDataFolder.ListFiles(Path.Combine(_recipeQueueFolder, executionId)));
|
||||
int firstIndex = stepFiles.Count;
|
||||
if (firstIndex == 0)
|
||||
return -1;
|
||||
// we always have only a handful of steps.
|
||||
foreach (var stepFile in stepFiles) {
|
||||
int stepOrder = Int32.Parse(stepFile.Substring(stepFile.LastIndexOf('/') + 1));
|
||||
if (firstIndex > stepOrder)
|
||||
firstIndex = stepOrder;
|
||||
}
|
||||
|
||||
return firstIndex;
|
||||
}
|
||||
|
||||
private int GetLastStepIndex(string executionId) {
|
||||
int lastIndex = -1;
|
||||
var stepFiles = _appDataFolder.ListFiles(Path.Combine(_recipeQueueFolder, executionId));
|
||||
// we always have only a handful of steps.
|
||||
foreach (var stepFile in stepFiles) {
|
||||
int stepOrder = Int32.Parse(stepFile.Substring(stepFile.LastIndexOf('/') + 1));
|
||||
if (stepOrder > lastIndex)
|
||||
lastIndex = stepOrder;
|
||||
}
|
||||
|
||||
return lastIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,14 +36,22 @@ namespace Orchard.FileSystems.AppData {
|
||||
}
|
||||
|
||||
private void MakeDestinationFileNameAvailable(string destinationFileName) {
|
||||
bool isDirectory = Directory.Exists(destinationFileName);
|
||||
// Try deleting the destination first
|
||||
try {
|
||||
File.Delete(destinationFileName);
|
||||
if (isDirectory)
|
||||
Directory.Delete(destinationFileName);
|
||||
else
|
||||
File.Delete(destinationFileName);
|
||||
}
|
||||
catch {
|
||||
// We land here if the file is in use, for example. Let's move on.
|
||||
}
|
||||
|
||||
if (isDirectory && Directory.Exists(destinationFileName)) {
|
||||
Logger.Warning("Could not delete recipe execution folder {0} under \"App_Data\" folder", destinationFileName);
|
||||
return;
|
||||
}
|
||||
// If destination doesn't exist, we are good
|
||||
if (!File.Exists(destinationFileName))
|
||||
return;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using Orchard.Recipes.Models;
|
||||
using Orchard.Recipes.Models;
|
||||
|
||||
namespace Orchard.Recipes.Services {
|
||||
public interface IRecipeStepQueue : ISingletonDependency {
|
||||
void Enqueue(string executionId, RecipeStep step);
|
||||
Tuple<RecipeStep, string> Dequeue(string executionId);
|
||||
RecipeStep Dequeue(string executionId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user