mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-14 19:04:51 +08:00
Adding a background task sweep generator. Updated cms page scheduler processing as a background task.
--HG-- extra : convert_revision : svn%3A5ff7c347-ad56-4c35-b696-ccb81de16e03/trunk%4042426
This commit is contained in:
@@ -38,7 +38,8 @@ namespace Orchard.Tests.Environment {
|
||||
new[] { modelBinderProvider1, modelBinderProvider2 },
|
||||
modelBinderPublisher,
|
||||
new ViewEngineCollection(),
|
||||
new Moq.Mock<IPackageManager>().Object);
|
||||
new Moq.Mock<IPackageManager>().Object,
|
||||
Enumerable.Empty<IOrchardShellEvents>());
|
||||
|
||||
runtime.Activate();
|
||||
|
||||
|
77
src/Orchard.Tests/EventsTests.cs
Normal file
77
src/Orchard.Tests/EventsTests.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using Orchard.Logging;
|
||||
|
||||
namespace Orchard.Tests {
|
||||
[TestFixture]
|
||||
public class EventsTests {
|
||||
[Test]
|
||||
public void AllEventsAreCalled() {
|
||||
var events = new ITestEvents[] { new FooSink(), new BarSink() };
|
||||
|
||||
events.Invoke(x => x.Hello("world"), NullLogger.Instance);
|
||||
|
||||
Assert.That(events.OfType<FooSink>().Single().Name, Is.EqualTo("world"));
|
||||
Assert.That(events.OfType<BarSink>().Single().Name, Is.EqualTo("world"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnExceptionShouldBeLoggedAndOtherEventsWillBeFired() {
|
||||
var events = new ITestEvents[] { new FooSink(), new CrashSink(), new BarSink() };
|
||||
|
||||
var logger = new TestLogger();
|
||||
|
||||
events.Invoke(x => x.Hello("world"), logger);
|
||||
|
||||
Assert.That(events.OfType<FooSink>().Single().Name, Is.EqualTo("world"));
|
||||
Assert.That(events.OfType<BarSink>().Single().Name, Is.EqualTo("world"));
|
||||
Assert.That(logger.LogException, Is.TypeOf<ApplicationException>());
|
||||
Assert.That(logger.LogException, Has.Property("Message").EqualTo("Illegal name 'world'"));
|
||||
}
|
||||
|
||||
private class TestLogger : ILogger {
|
||||
public bool IsEnabled(LogLevel level) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Log(LogLevel level, Exception exception, string format, params object[] args) {
|
||||
LogException = exception;
|
||||
LogFormat = format;
|
||||
LogArgs = args;
|
||||
}
|
||||
|
||||
public Exception LogException { get; set; }
|
||||
public string LogFormat { get; set; }
|
||||
public object[] LogArgs { get; set; }
|
||||
}
|
||||
|
||||
private interface ITestEvents : IEvents {
|
||||
void Hello(string name);
|
||||
}
|
||||
|
||||
private class FooSink : ITestEvents {
|
||||
void ITestEvents.Hello(string name) {
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
private class BarSink : ITestEvents {
|
||||
void ITestEvents.Hello(string name) {
|
||||
Name = name;
|
||||
}
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
|
||||
private class CrashSink : ITestEvents {
|
||||
void ITestEvents.Hello(string name) {
|
||||
throw new ApplicationException("Illegal name '" + name + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -104,6 +104,7 @@
|
||||
<Compile Include="Environment\DefaultOrchardHostTests.cs" />
|
||||
<Compile Include="Environment\DefaultOrchardShellTests.cs" />
|
||||
<Compile Include="Environment\OrchardStarterTests.cs" />
|
||||
<Compile Include="EventsTests.cs" />
|
||||
<Compile Include="Localization\NullLocalizerTests.cs" />
|
||||
<Compile Include="Logging\LoggingModuleTests.cs" />
|
||||
<Compile Include="Models\ContentQueryTests.cs" />
|
||||
@@ -127,6 +128,7 @@
|
||||
<Compile Include="Mvc\OrchardControllerIdentificationStrategyTests.cs" />
|
||||
<Compile Include="Mvc\RouteCollectionPublisherTests.cs" />
|
||||
<Compile Include="Mvc\Routes\StandardPackageRouteProviderTests.cs" />
|
||||
<Compile Include="Tasks\SweepGeneratorTests.cs" />
|
||||
<Compile Include="UI\Notify\NotifierTests.cs" />
|
||||
<Compile Include="UI\Notify\NotifyFilterTests.cs" />
|
||||
<Compile Include="Packages\PackageFoldersTests.cs" />
|
||||
|
43
src/Orchard.Tests/Tasks/SweepGeneratorTests.cs
Normal file
43
src/Orchard.Tests/Tasks/SweepGeneratorTests.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Autofac.Builder;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Orchard.Tasks;
|
||||
|
||||
namespace Orchard.Tests.Tasks {
|
||||
[TestFixture]
|
||||
public class SweepGeneratorTests {
|
||||
|
||||
[Test]
|
||||
public void DoWorkShouldSendHeartbeatToTaskManager() {
|
||||
var taskManager = new Mock<IBackgroundService>();
|
||||
|
||||
var builder = new ContainerBuilder();
|
||||
builder.Register(taskManager.Object);
|
||||
var container = builder.Build();
|
||||
|
||||
var heartbeatSource = new SweepGenerator(container);
|
||||
heartbeatSource.DoWork();
|
||||
taskManager.Verify(x => x.Sweep(), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ActivatedEventShouldStartTimer() {
|
||||
var taskManager = new Mock<IBackgroundService>();
|
||||
|
||||
var builder = new ContainerBuilder();
|
||||
builder.Register(taskManager.Object);
|
||||
var container = builder.Build();
|
||||
|
||||
var heartbeatSource = new SweepGenerator(container) {
|
||||
Interval = TimeSpan.FromMilliseconds(25)
|
||||
};
|
||||
|
||||
taskManager.Verify(x => x.Sweep(), Times.Never());
|
||||
heartbeatSource.Activated();
|
||||
System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(80));
|
||||
heartbeatSource.Terminating();
|
||||
taskManager.Verify(x => x.Sweep(), Times.AtLeastOnce());
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,11 +6,11 @@ using JetBrains.Annotations;
|
||||
using Orchard.CmsPages.Models;
|
||||
using Orchard.Data;
|
||||
using Orchard.Services;
|
||||
using Orchard.Tasks;
|
||||
|
||||
namespace Orchard.CmsPages.Services {
|
||||
public interface IPageScheduler : IDependency {
|
||||
public interface IPageScheduler : IBackgroundTask {
|
||||
void AddPublishTask(PageRevision revision, DateTime moment);
|
||||
void Sweep();
|
||||
void ClearTasks(Page page);
|
||||
}
|
||||
|
||||
|
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Web.Hosting;
|
||||
using FluentNHibernate.Automapping;
|
||||
using FluentNHibernate.Automapping.Alterations;
|
||||
using FluentNHibernate.Cfg;
|
||||
@@ -29,8 +30,9 @@ namespace Orchard.Data {
|
||||
// that would eventually imply the need for configuration against one or more actual sources
|
||||
// and a means to enlist record types from active packages into correct session factory
|
||||
|
||||
var database =
|
||||
SQLiteConfiguration.Standard.UsingFile(HttpContext.Current.Server.MapPath("~/App_Data/hack.db"));
|
||||
var hackPath = HostingEnvironment.MapPath("~/App_Data/hack.db");
|
||||
|
||||
var database = SQLiteConfiguration.Standard.UsingFile(hackPath);
|
||||
|
||||
var recordTypes = _compositionStrategy.GetRecordTypes();
|
||||
|
||||
|
@@ -3,9 +3,11 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web.Mvc;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Mvc.ModelBinders;
|
||||
using Orchard.Mvc.Routes;
|
||||
using Orchard.Packages;
|
||||
using Orchard.Tasks;
|
||||
|
||||
namespace Orchard.Environment {
|
||||
public class DefaultOrchardShell : IOrchardShell {
|
||||
@@ -15,6 +17,7 @@ namespace Orchard.Environment {
|
||||
private readonly IModelBinderPublisher _modelBinderPublisher;
|
||||
private readonly ViewEngineCollection _viewEngines;
|
||||
private readonly IPackageManager _packageManager;
|
||||
private readonly IEnumerable<IOrchardShellEvents> _events;
|
||||
|
||||
public DefaultOrchardShell(
|
||||
IEnumerable<IRouteProvider> routeProviders,
|
||||
@@ -22,15 +25,21 @@ namespace Orchard.Environment {
|
||||
IEnumerable<IModelBinderProvider> modelBinderProviders,
|
||||
IModelBinderPublisher modelBinderPublisher,
|
||||
ViewEngineCollection viewEngines,
|
||||
IPackageManager packageManager) {
|
||||
IPackageManager packageManager,
|
||||
IEnumerable<IOrchardShellEvents> events) {
|
||||
_routeProviders = routeProviders;
|
||||
_routePublisher = routePublisher;
|
||||
_modelBinderProviders = modelBinderProviders;
|
||||
_modelBinderPublisher = modelBinderPublisher;
|
||||
_viewEngines = viewEngines;
|
||||
_packageManager = packageManager;
|
||||
_events = events;
|
||||
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
|
||||
static IEnumerable<string> OrchardLocationFormats() {
|
||||
return new[] {
|
||||
@@ -49,8 +58,8 @@ namespace Orchard.Environment {
|
||||
_routePublisher.Publish(_routeProviders.SelectMany(provider => provider.GetRoutes()));
|
||||
_modelBinderPublisher.Publish(_modelBinderProviders.SelectMany(provider => provider.GetModelBinders()));
|
||||
|
||||
|
||||
var viewEngine = ViewEngines.Engines.OfType<VirtualPathProviderViewEngine>().Single();
|
||||
|
||||
var viewEngine = _viewEngines.OfType<VirtualPathProviderViewEngine>().Single();
|
||||
viewEngine.AreaViewLocationFormats = OrchardLocationFormats()
|
||||
.Concat(viewEngine.AreaViewLocationFormats)
|
||||
.Distinct()
|
||||
@@ -66,8 +75,16 @@ namespace Orchard.Environment {
|
||||
.Concat(viewEngine.PartialViewLocationFormats)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
_events.Invoke(x => x.Activated(), Logger);
|
||||
}
|
||||
|
||||
|
||||
public void Terminate() {
|
||||
_events.Invoke(x => x.Terminating(), Logger);
|
||||
}
|
||||
|
||||
|
||||
private static string ModelsLocationFormat(PackageDescriptor descriptor) {
|
||||
return Path.Combine(Path.Combine(descriptor.Location, descriptor.Name), "Views/Models/{0}.ascx");
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
namespace Orchard.Environment {
|
||||
public interface IOrchardShell {
|
||||
void Activate();
|
||||
void Terminate();
|
||||
}
|
||||
}
|
6
src/Orchard/Environment/IOrchardShellEvents.cs
Normal file
6
src/Orchard/Environment/IOrchardShellEvents.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Orchard.Environment {
|
||||
public interface IOrchardShellEvents : IEvents {
|
||||
void Activated();
|
||||
void Terminating();
|
||||
}
|
||||
}
|
24
src/Orchard/IEvents.cs
Normal file
24
src/Orchard/IEvents.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Logging;
|
||||
|
||||
namespace Orchard {
|
||||
public interface IEvents : IDependency {
|
||||
}
|
||||
|
||||
public static class EventsExtensions {
|
||||
public static void Invoke<TEvents>(this IEnumerable<TEvents> events, Action<TEvents> dispatch, ILogger logger) where TEvents : IEvents {
|
||||
foreach (var sink in events) {
|
||||
try {
|
||||
dispatch(sink);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.Error(ex, "{2} thrown from {0} by {1}",
|
||||
typeof(TEvents).Name,
|
||||
sink.GetType().FullName,
|
||||
ex.GetType().Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -118,6 +118,8 @@
|
||||
<Compile Include="Controllers\HomeController.cs" />
|
||||
<Compile Include="Data\Conventions\AttributeCollectionConvention.cs" />
|
||||
<Compile Include="Data\Conventions\CascadeAllDeleteOrphanAttribute.cs" />
|
||||
<Compile Include="Environment\IOrchardShellEvents.cs" />
|
||||
<Compile Include="IEvents.cs" />
|
||||
<Compile Include="Localization\IText.cs" />
|
||||
<Compile Include="Localization\LocalizationModule.cs" />
|
||||
<Compile Include="Localization\LocalizationUtilities.cs" />
|
||||
@@ -194,6 +196,9 @@
|
||||
<Compile Include="Settings\ISite.cs" />
|
||||
<Compile Include="Settings\ISiteService.cs" />
|
||||
<Compile Include="Settings\SettingsModule.cs" />
|
||||
<Compile Include="Tasks\BackgroundService.cs" />
|
||||
<Compile Include="Tasks\IBackgroundTask.cs" />
|
||||
<Compile Include="Tasks\SweepGenerator.cs" />
|
||||
<Compile Include="UI\Menus\AdminMenuFilter.cs" />
|
||||
<Compile Include="UI\Models\ModelTemplate.cs" />
|
||||
<Compile Include="UI\Navigation\NavigationBuilder.cs" />
|
||||
|
25
src/Orchard/Tasks/BackgroundService.cs
Normal file
25
src/Orchard/Tasks/BackgroundService.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Logging;
|
||||
|
||||
namespace Orchard.Tasks {
|
||||
|
||||
public interface IBackgroundService : IDependency {
|
||||
void Sweep();
|
||||
}
|
||||
|
||||
public class BackgroundService : IBackgroundService {
|
||||
private readonly IEnumerable<IBackgroundTask> _tasks;
|
||||
|
||||
public BackgroundService(IEnumerable<IBackgroundTask> tasks) {
|
||||
_tasks = tasks;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public void Sweep() {
|
||||
_tasks.Invoke(task => task.Sweep(), Logger);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
5
src/Orchard/Tasks/IBackgroundTask.cs
Normal file
5
src/Orchard/Tasks/IBackgroundTask.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace Orchard.Tasks {
|
||||
public interface IBackgroundTask : IEvents {
|
||||
void Sweep();
|
||||
}
|
||||
}
|
97
src/Orchard/Tasks/SweepGenerator.cs
Normal file
97
src/Orchard/Tasks/SweepGenerator.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Timers;
|
||||
using Autofac;
|
||||
using Autofac.Integration.Web;
|
||||
using Orchard.Environment;
|
||||
using Orchard.Logging;
|
||||
|
||||
namespace Orchard.Tasks {
|
||||
public class SweepGenerator : IOrchardShellEvents {
|
||||
private readonly IContainer _container;
|
||||
private Timer _timer;
|
||||
|
||||
public SweepGenerator(IContainer container) {
|
||||
_container = container;
|
||||
_timer = new Timer();
|
||||
_timer.Elapsed += Elapsed;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public TimeSpan Interval {
|
||||
get { return TimeSpan.FromMilliseconds(_timer.Interval); }
|
||||
set { _timer.Interval = value.Milliseconds; }
|
||||
}
|
||||
|
||||
public void Activated() {
|
||||
lock (_timer) {
|
||||
_timer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public void Terminating() {
|
||||
lock (_timer) {
|
||||
_timer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
void Elapsed(object sender, ElapsedEventArgs e) {
|
||||
// current implementation disallows re-entrancy
|
||||
if (!System.Threading.Monitor.TryEnter(_timer))
|
||||
return;
|
||||
|
||||
try {
|
||||
if (_timer.Enabled) {
|
||||
DoWork();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Logger.Warning(ex, "Problem in background tasks");
|
||||
}
|
||||
finally {
|
||||
System.Threading.Monitor.Exit(_timer);
|
||||
}
|
||||
}
|
||||
|
||||
public void DoWork() {
|
||||
// makes an inner container, similar to the per-request container
|
||||
|
||||
var containerProvider = new ContainerProvider(_container);
|
||||
try {
|
||||
var requestContainer = containerProvider.RequestContainer;
|
||||
|
||||
// also inject this instance in case anyone asks for the container provider
|
||||
requestContainer.Build(builder => builder.Register(containerProvider).As<IContainerProvider>());
|
||||
|
||||
// resolve the manager and invoke it
|
||||
var manager = requestContainer.Resolve<IBackgroundService>();
|
||||
manager.Sweep();
|
||||
}
|
||||
finally{
|
||||
// shut everything down again
|
||||
containerProvider.DisposeRequestContainer();
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerProvider : IContainerProvider {
|
||||
public ContainerProvider(IContainer applicationContainer) {
|
||||
// explicitly create a request container for the life of this object
|
||||
ApplicationContainer = applicationContainer;
|
||||
RequestContainer = applicationContainer.CreateInnerContainer();
|
||||
}
|
||||
|
||||
public void DisposeRequestContainer() {
|
||||
var disposeContainer = RequestContainer;
|
||||
RequestContainer = null;
|
||||
|
||||
if (disposeContainer != null)
|
||||
disposeContainer.Dispose();
|
||||
}
|
||||
|
||||
public IContainer ApplicationContainer { get; private set; }
|
||||
|
||||
public IContainer RequestContainer { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user