Finishing the implementation of recipe execution

StepQueue is now based off an IAppDataFolder

--HG--
branch : recipe
This commit is contained in:
Suha Can
2011-02-16 14:41:10 -08:00
parent eb24ea67b0
commit 8f699ea418
5 changed files with 117 additions and 19 deletions

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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);
}
}