mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-21 19:34:40 +08:00
RecipeJournal implementation.
- IStorageProvider based journal implementation for the recipe pipeline. - Was needed as the existing reporting capability was found insufficient. --HG-- branch : recipe
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
@@ -68,6 +67,7 @@ namespace Orchard.Tests.Modules.Recipes.Services {
|
||||
builder.RegisterType<RecipeHarvester>().As<IRecipeHarvester>();
|
||||
builder.RegisterType<RecipeStepExecutor>().As<IRecipeStepExecutor>();
|
||||
builder.RegisterType<StubStepQueue>().As<IRecipeStepQueue>().InstancePerLifetimeScope();
|
||||
builder.RegisterType<StubRecipeJournal>().As<IRecipeJournal>();
|
||||
builder.RegisterType<StubRecipeScheduler>().As<IRecipeScheduler>();
|
||||
builder.RegisterType<ExtensionManager>().As<IExtensionManager>();
|
||||
builder.RegisterType<StubAppDataFolder>().As<IAppDataFolder>();
|
||||
@@ -145,6 +145,28 @@ namespace Orchard.Tests.Modules.Recipes.Services {
|
||||
}
|
||||
}
|
||||
|
||||
public class StubRecipeJournal : IRecipeJournal {
|
||||
public void ExecutionStart(string executionId) {
|
||||
}
|
||||
|
||||
public void ExecutionComplete(string executionId) {
|
||||
}
|
||||
|
||||
public void ExecutionFailed(string executionId) {
|
||||
}
|
||||
|
||||
public void WriteJournalEntry(string executionId, string message) {
|
||||
}
|
||||
|
||||
public RecipeJournal GetRecipeJournal(string executionId) {
|
||||
return new RecipeJournal();
|
||||
}
|
||||
|
||||
public RecipeStatus GetRecipeStatus(string executionId) {
|
||||
return RecipeStatus.Complete;
|
||||
}
|
||||
}
|
||||
|
||||
public class StubStepQueue : IRecipeStepQueue {
|
||||
readonly Queue<RecipeStep> _queue = new Queue<RecipeStep>();
|
||||
|
||||
@@ -171,8 +193,12 @@ namespace Orchard.Tests.Modules.Recipes.Services {
|
||||
|
||||
public class CustomRecipeHandler : IRecipeHandler {
|
||||
public static string AttributeValue;
|
||||
public string[] _handles = {"Module", "Theme", "Migration", "CleanUpInactive", "Custom1", "Custom2", "Command", "Metadata", "Feature", "Settings"};
|
||||
|
||||
public void ExecuteRecipeStep(RecipeContext recipeContext) {
|
||||
if (_handles.Contains(recipeContext.RecipeStep.Name)) {
|
||||
recipeContext.Executed = true;
|
||||
}
|
||||
if (recipeContext.RecipeStep.Name == "Custom1") {
|
||||
foreach (var attribute in recipeContext.RecipeStep.Step.Attributes().Where(attribute => attribute.Name == "attr1")) {
|
||||
AttributeValue = attribute.Value;
|
||||
|
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml.Linq;
|
||||
using Orchard.FileSystems.Media;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Logging;
|
||||
@@ -7,6 +10,7 @@ using Orchard.Recipes.Models;
|
||||
namespace Orchard.Recipes.Services {
|
||||
public class RecipeJournalManager : IRecipeJournal {
|
||||
private readonly IStorageProvider _storageProvider;
|
||||
private readonly string _recipeJournalFolder = "RecipeJournal" + Path.DirectorySeparatorChar;
|
||||
|
||||
public RecipeJournalManager(IStorageProvider storageProvider) {
|
||||
_storageProvider = storageProvider;
|
||||
@@ -18,36 +22,103 @@ namespace Orchard.Recipes.Services {
|
||||
public Localizer T { get; set; }
|
||||
ILogger Logger { get; set; }
|
||||
|
||||
public void StartExecution(string executionId) {
|
||||
throw new NotImplementedException();
|
||||
public void ExecutionStart(string executionId) {
|
||||
var executionJournal = GetJournalFile(executionId);
|
||||
var xElement = XElement.Parse(ReadJournal(executionJournal));
|
||||
xElement.Element("Status").Value = "Started";
|
||||
WriteJournal(executionJournal, xElement);
|
||||
}
|
||||
|
||||
public void ExecutionComplete(string executionId) {
|
||||
throw new NotImplementedException();
|
||||
var executionJournal = GetJournalFile(executionId);
|
||||
var xElement = XElement.Parse(ReadJournal(executionJournal));
|
||||
xElement.Element("Status").Value = "Complete";
|
||||
WriteJournal(executionJournal, xElement);
|
||||
}
|
||||
|
||||
public void ExecutionFailed(string executionId) {
|
||||
throw new NotImplementedException();
|
||||
var executionJournal = GetJournalFile(executionId);
|
||||
var xElement = XElement.Parse(ReadJournal(executionJournal));
|
||||
xElement.Element("Status").Value = "Failed";
|
||||
WriteJournal(executionJournal, xElement);
|
||||
}
|
||||
|
||||
public void WriteJournalEntry(string executionId, string message) {
|
||||
throw new NotImplementedException();
|
||||
var executionJournal = GetJournalFile(executionId);
|
||||
var xElement = XElement.Parse(ReadJournal(executionJournal));
|
||||
var journalEntry = new XElement("Message", message);
|
||||
xElement.Add(journalEntry);
|
||||
WriteJournal(executionJournal, xElement);
|
||||
}
|
||||
|
||||
public RecipeJournal GetRecipeJournal(string executionId) {
|
||||
throw new NotImplementedException();
|
||||
var executionJournal = GetJournalFile(executionId);
|
||||
var xElement = XElement.Parse(ReadJournal(executionJournal));
|
||||
|
||||
var journal = new RecipeJournal { ExecutionId = executionId };
|
||||
var messages = new List<JournalMessage>();
|
||||
|
||||
journal.Status = ReadStatusFromJournal(xElement);
|
||||
foreach (var message in xElement.Elements("Message")) {
|
||||
messages.Add(new JournalMessage {Message = message.Value});
|
||||
}
|
||||
journal.Messages = messages;
|
||||
|
||||
return journal;
|
||||
}
|
||||
|
||||
public RecipeStatus GetRecipeStatus(string executionId) {
|
||||
var executionJournal = GetJournalFile(executionId);
|
||||
var xElement = XElement.Parse(ReadJournal(executionJournal));
|
||||
|
||||
return ReadStatusFromJournal(xElement);
|
||||
}
|
||||
|
||||
private IStorageFile GetJournalFile(string executionId) {
|
||||
IStorageFile journalFile;
|
||||
var journalPath = Path.Combine(_recipeJournalFolder, executionId);
|
||||
try {
|
||||
journalFile = _storageProvider.GetFile(executionId);
|
||||
_storageProvider.TryCreateFolder(_recipeJournalFolder);
|
||||
journalFile = _storageProvider.GetFile(journalPath);
|
||||
}
|
||||
catch (ArgumentException) {
|
||||
journalFile = _storageProvider.CreateFile(executionId);
|
||||
journalFile = _storageProvider.CreateFile(journalPath);
|
||||
var recipeStepElement = new XElement("RecipeJournal");
|
||||
recipeStepElement.Add(new XElement("Status", "Unknown"));
|
||||
WriteJournal(journalFile, recipeStepElement);
|
||||
}
|
||||
|
||||
return journalFile;
|
||||
}
|
||||
|
||||
private static string ReadJournal(IStorageFile executionJournal) {
|
||||
using (var stream = executionJournal.OpenRead()) {
|
||||
using (var streamReader = new StreamReader(stream)) {
|
||||
return streamReader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteJournal(IStorageFile journalFile, XElement journal) {
|
||||
string content = journal.ToString();
|
||||
using (var stream = journalFile.OpenWrite()) {
|
||||
using (var tw = new StreamWriter(stream)) {
|
||||
tw.Write(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static RecipeStatus ReadStatusFromJournal(XElement xElement) {
|
||||
switch (xElement.Element("Status").Value) {
|
||||
case "Started":
|
||||
return RecipeStatus.Started;
|
||||
case "Complete":
|
||||
return RecipeStatus.Complete;
|
||||
case "Failed":
|
||||
return RecipeStatus.Failed;
|
||||
default:
|
||||
return RecipeStatus.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,10 +7,12 @@ namespace Orchard.Recipes.Services {
|
||||
public class RecipeManager : IRecipeManager {
|
||||
private readonly IRecipeStepQueue _recipeStepQueue;
|
||||
private readonly IRecipeScheduler _recipeScheduler;
|
||||
private readonly IRecipeJournal _recipeJournal;
|
||||
|
||||
public RecipeManager(IRecipeStepQueue recipeStepQueue, IRecipeScheduler recipeScheduler) {
|
||||
public RecipeManager(IRecipeStepQueue recipeStepQueue, IRecipeScheduler recipeScheduler, IRecipeJournal recipeJournal) {
|
||||
_recipeStepQueue = recipeStepQueue;
|
||||
_recipeScheduler = recipeScheduler;
|
||||
_recipeJournal = recipeJournal;
|
||||
|
||||
Logger = NullLogger.Instance;
|
||||
T = NullLocalizer.Instance;
|
||||
@@ -24,7 +26,8 @@ namespace Orchard.Recipes.Services {
|
||||
return null;
|
||||
|
||||
var executionId = Guid.NewGuid().ToString("n");
|
||||
// TODO: Run each step inside a transaction boundary.
|
||||
_recipeJournal.ExecutionStart(executionId);
|
||||
|
||||
foreach (var recipeStep in recipe.RecipeSteps) {
|
||||
_recipeStepQueue.Enqueue(executionId, recipeStep);
|
||||
}
|
||||
|
@@ -7,10 +7,12 @@ using Orchard.Recipes.Models;
|
||||
namespace Orchard.Recipes.Services {
|
||||
public class RecipeStepExecutor : IRecipeStepExecutor {
|
||||
private readonly IRecipeStepQueue _recipeStepQueue;
|
||||
private readonly IRecipeJournal _recipeJournal;
|
||||
private readonly IEnumerable<IRecipeHandler> _recipeHandlers;
|
||||
|
||||
public RecipeStepExecutor(IRecipeStepQueue recipeStepQueue, IEnumerable<IRecipeHandler> recipeHandlers) {
|
||||
public RecipeStepExecutor(IRecipeStepQueue recipeStepQueue, IRecipeJournal recipeJournal, IEnumerable<IRecipeHandler> recipeHandlers) {
|
||||
_recipeStepQueue = recipeStepQueue;
|
||||
_recipeJournal = recipeJournal;
|
||||
_recipeHandlers = recipeHandlers;
|
||||
|
||||
Logger = NullLogger.Instance;
|
||||
@@ -23,8 +25,10 @@ namespace Orchard.Recipes.Services {
|
||||
public bool ExecuteNextStep(string executionId) {
|
||||
var nextRecipeStep= _recipeStepQueue.Dequeue(executionId);
|
||||
if (nextRecipeStep == null) {
|
||||
_recipeJournal.ExecutionComplete(executionId);
|
||||
return false;
|
||||
}
|
||||
_recipeJournal.WriteJournalEntry(executionId, string.Format("Executing step {0}.", nextRecipeStep.Name));
|
||||
var recipeContext = new RecipeContext { RecipeStep = nextRecipeStep, Executed = false };
|
||||
try {
|
||||
foreach (var recipeHandler in _recipeHandlers) {
|
||||
@@ -34,11 +38,15 @@ namespace Orchard.Recipes.Services {
|
||||
catch(Exception exception) {
|
||||
Logger.Error(exception, "Recipe execution {0} was cancelled because a step failed to execute", executionId);
|
||||
while (_recipeStepQueue.Dequeue(executionId) != null) ;
|
||||
_recipeJournal.ExecutionFailed(executionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!recipeContext.Executed) {
|
||||
Logger.Error("Could not execute recipe step '{0}' because the recipe handler was not found.", recipeContext.RecipeStep.Name);
|
||||
while (_recipeStepQueue.Dequeue(executionId) != null) ;
|
||||
_recipeJournal.ExecutionFailed(executionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -3,7 +3,7 @@
|
||||
namespace Orchard.Recipes.Models {
|
||||
public class RecipeJournal {
|
||||
public string ExecutionId { get; set; }
|
||||
public RecipeJournalStatus Status { get; set; }
|
||||
public RecipeStatus Status { get; set; }
|
||||
public IEnumerable<JournalMessage> Messages { get; set; }
|
||||
}
|
||||
|
||||
@@ -11,8 +11,9 @@ namespace Orchard.Recipes.Models {
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public enum RecipeJournalStatus {
|
||||
Running,
|
||||
public enum RecipeStatus {
|
||||
Unknown,
|
||||
Started,
|
||||
Complete,
|
||||
Failed
|
||||
}
|
||||
|
@@ -2,10 +2,11 @@
|
||||
|
||||
namespace Orchard.Recipes.Services {
|
||||
public interface IRecipeJournal : IDependency {
|
||||
void StartExecution(string executionId);
|
||||
void ExecutionStart(string executionId);
|
||||
void ExecutionComplete(string executionId);
|
||||
void ExecutionFailed(string executionId);
|
||||
void WriteJournalEntry(string executionId, string message);
|
||||
RecipeJournal GetRecipeJournal(string executionId);
|
||||
RecipeStatus GetRecipeStatus(string executionId);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user