Fix scheduled publishing of pages

Data model is now correctly updated when publishing, creating drafts, scheduling publishing, etc. There is no code left in the pages module which directly sets the CommonAspect dates. All is properly done through the CommonAspectHandler, the ContentManager and the PublishingTaskManager.

There is a remaining issue with the background scheduler which is unable to run scheduled tasks (exception thrown by autofac). This will be investigated later.

--HG--
extra : convert_revision : svn%3A5ff7c347-ad56-4c35-b696-ccb81de16e03/trunk%4045944
This commit is contained in:
rpaquay
2010-01-25 03:44:51 +00:00
parent d135ed776d
commit 031582d156
18 changed files with 114 additions and 85 deletions

View File

@@ -105,6 +105,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scheduling\Records\ScheduledTaskRecord.cs" />
<Compile Include="Scheduling\Services\PublishingTaskHandler.cs" />
<Compile Include="Scheduling\Services\PublishingTaskManager.cs" />
<Compile Include="Scheduling\Services\ScheduledTaskManager.cs" />
<Compile Include="Scheduling\Services\ScheduledTaskExecutor.cs" />
<Compile Include="Scheduling\Models\Task.cs" />

View File

@@ -1,7 +1,7 @@
using System;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Scheduling.Records;
using Orchard.Tasks.Scheduling;
namespace Orchard.Core.Scheduling.Models {
public class Task : IScheduledTask {

View File

@@ -0,0 +1,33 @@
using System;
using System.Linq;
using Orchard.ContentManagement;
using Orchard.Tasks.Scheduling;
namespace Orchard.Core.Scheduling.Services {
public class PublishingTaskManager : IPublishingTaskManager {
private const string PublishTaskType = "Publish";
private const string UnpublishTaskType = "Unpublish";
private readonly IScheduledTaskManager _scheduledTaskManager;
public PublishingTaskManager(IScheduledTaskManager scheduledTaskManager) {
_scheduledTaskManager = scheduledTaskManager;
}
public IScheduledTask GetPublishTask(ContentItem item) {
return _scheduledTaskManager
.GetTasks(item)
.Where(task => task.TaskType == PublishTaskType)
.SingleOrDefault();
}
public void Publish(ContentItem item, DateTime scheduledUtc) {
DeleteTasks(item);
_scheduledTaskManager.CreateTask(PublishTaskType, scheduledUtc, item);
}
public void DeleteTasks(ContentItem item) {
_scheduledTaskManager.DeleteTasks(item, task => task.TaskType == PublishTaskType || task.TaskType == UnpublishTaskType);
}
}
}

View File

@@ -1,10 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Scheduling.Models;
using Orchard.Core.Scheduling.Records;
using Orchard.Data;
@@ -13,7 +10,6 @@ using Orchard.Tasks.Scheduling;
using Orchard.Utility;
namespace Orchard.Core.Scheduling.Services {
[UsedImplicitly]
public class ScheduledTaskManager : IScheduledTaskManager {
private readonly IRepository<ScheduledTaskRecord> _repository;
@@ -46,5 +42,17 @@ namespace Orchard.Core.Scheduling.Services {
.Cast<IScheduledTask>()
.ToReadOnlyCollection();
}
public void DeleteTasks(ContentItem contentItem, Func<IScheduledTask, bool> predicate) {
//TEMP: Is this thread safe? Does it matter?
var tasks = _repository
.Fetch(x => x.ContentItemVersionRecord.ContentItemRecord == contentItem.Record);
foreach (var task in tasks) {
if (predicate(new Task(Services.ContentManager, task))) {
_repository.Delete(task);
}
}
}
}
}

View File

@@ -119,25 +119,10 @@ namespace Orchard.Pages.Controllers {
if (!Services.Authorizer.Authorize(Permissions.EditPages, T("Couldn't create page")))
return new HttpUnauthorizedResult();
//TODO: (erikpo) Move this duplicate code somewhere else
DateTime? publishDate = null;
bool publishNow = false;
if (string.Equals(Request.Form["Command"], "PublishNow")) {
publishNow = true;
}
else if (string.Equals(Request.Form["Command"], "PublishLater")) {
DateTime publishDateValue;
if (DateTime.TryParse(Request.Form["Published"], out publishDateValue)) {
publishDate = publishDateValue;
}
}
// Validate form input
var page = Services.ContentManager.New<Page>("page");
model.Page = Services.ContentManager.UpdateEditorModel(page, this);
if (!publishNow && publishDate != null)
model.Page.Item.Published = publishDate.Value;
if (!ModelState.IsValid) {
Services.TransactionManager.Cancel();
return View(model);
@@ -145,15 +130,20 @@ namespace Orchard.Pages.Controllers {
Services.ContentManager.Create(model.Page.Item.ContentItem, VersionOptions.Draft);
if (publishNow)
Services.ContentManager.Publish(model.Page.Item.ContentItem);
if (publishNow)
Services.Notifier.Information(T("Page has been published"));
else if (publishDate != null)
Services.Notifier.Information(T("Page has been scheduled for publishing"));
else
Services.Notifier.Information(T("Page draft has been saved"));
// Execute publish command
switch (Request.Form["Command"]) {
case "PublishNow":
_pageService.Publish(model.Page.Item);
Services.Notifier.Information(T("Page has been published"));
break;
case "PublishLater":
_pageService.Publish(model.Page.Item, model.Page.Item.ScheduledPublishUtc.Value);
Services.Notifier.Information(T("Page has been scheduled for publishing"));
break;
default:
Services.Notifier.Information(T("Page draft has been saved"));
break;
}
return RedirectToAction("Edit", "Admin", new { id = model.Page.Item.ContentItem.Id });
}
@@ -191,37 +181,24 @@ namespace Orchard.Pages.Controllers {
if (!ModelState.IsValid) {
Services.TransactionManager.Cancel();
return View(model);
}
//TODO: (erikpo) Move this duplicate code somewhere else
DateTime? publishDate = null;
bool publishNow = false;
if (string.Equals(Request.Form["Command"], "PublishNow")) {
publishNow = true;
// Execute publish command
switch (Request.Form["Command"]) {
case "PublishNow":
_pageService.Publish(model.Page.Item);
Services.Notifier.Information(T("Page has been published"));
break;
case "PublishLater":
_pageService.Publish(model.Page.Item, model.Page.Item.ScheduledPublishUtc.Value);
Services.Notifier.Information(T("Page has been scheduled for publishing"));
break;
default:
Services.Notifier.Information(T("Page draft has been saved"));
_pageService.Unpublish(page);
break;
}
else if (string.Equals(Request.Form["Command"], "PublishLater")) {
DateTime publishDateValue;
if (DateTime.TryParse(Request.Form["Published"], out publishDateValue)) {
publishDate = publishDateValue;
}
}
//TODO: (erikpo) Move this duplicate code somewhere else
if (publishNow)
_pageService.Publish(page);
else if (publishDate != null)
_pageService.Publish(page, publishDate.Value);
else
_pageService.Unpublish(page);
if (publishNow)
Services.Notifier.Information(T("Page has been published"));
else if (publishDate != null)
Services.Notifier.Information(T("Page has been scheduled for publishing"));
else
Services.Notifier.Information(T("Page draft has been saved"));
return RedirectToAction("Edit", "Admin", new { id = model.Page.Item.ContentItem.Id });
}

View File

@@ -2,7 +2,6 @@
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Routing;
using JetBrains.Annotations;
using Orchard.Core.Common.Models;
using Orchard.Core.Common.Services;
using Orchard.Localization;
@@ -13,7 +12,6 @@ using Orchard.Pages.Services;
using Orchard.UI.Notify;
namespace Orchard.Pages.Controllers {
[UsedImplicitly]
public class PageDriver : ContentItemDriver<Page> {
private readonly IPageService _pageService;
private readonly IRoutableService _routableService;

View File

@@ -57,12 +57,6 @@ namespace Orchard.Pages.Models {
}
}
public DateTime? Published {
get { return this.As<CommonAspect>().PublishedUtc; }
set { this.As<CommonAspect>().PublishedUtc = value; }
}
//[CascadeAllDeleteOrphan]
//public virtual IList<Scheduled> Scheduled { get; protected set; }
public DateTime? ScheduledPublishUtc { get; set;}
}
}

View File

@@ -28,6 +28,7 @@ namespace Orchard.Pages.Models {
Filters.Add(new ActivatingFilter<RoutableAspect>(PageDriver.ContentType.Name));
Filters.Add(new ActivatingFilter<BodyAspect>(PageDriver.ContentType.Name));
OnLoaded<Page>((context, p) => p.ScheduledPublishUtc = _pageService.GetScheduledPublishUtc(p));
OnPublished<Page>((context, p) => ProcessSlug(p));
}

View File

@@ -14,8 +14,9 @@ namespace Orchard.Pages.Services {
Page GetLatest(int id);
void Delete(Page page);
void Publish(Page page);
void Publish(Page page, DateTime publishDate);
void Publish(Page page, DateTime scheduledPublishUtc);
void Unpublish(Page page);
DateTime? GetScheduledPublishUtc(Page page);
}
public enum PageStatus {

View File

@@ -5,15 +5,16 @@ using Orchard.Pages.Models;
using Orchard.Core.Common.Records;
using Orchard.ContentManagement;
using Orchard.Services;
using Orchard.Tasks.Scheduling;
namespace Orchard.Pages.Services {
public class PageService : IPageService {
private readonly IContentManager _contentManager;
private readonly IClock _clock;
private readonly IPublishingTaskManager _publishingTaskManager;
public PageService(IContentManager contentManager, IClock clock) {
public PageService(IContentManager contentManager, IPublishingTaskManager publishingTaskManager) {
_contentManager = contentManager;
_clock = clock;
_publishingTaskManager = publishingTaskManager;
}
public IEnumerable<Page> Get() {
@@ -71,21 +72,26 @@ namespace Orchard.Pages.Services {
}
public void Delete(Page page) {
_publishingTaskManager.DeleteTasks(page.ContentItem);
_contentManager.Remove(page.ContentItem);
}
public void Publish(Page page) {
_publishingTaskManager.DeleteTasks(page.ContentItem);
_contentManager.Publish(page.ContentItem);
}
public void Publish(Page page, DateTime publishDate) {
//TODO: Implement task scheduling
//if (page.Published != null && page.Published.Value >= _clock.UtcNow)
// _contentManager.Unpublish(page.ContentItem);
public void Publish(Page page, DateTime scheduledPublishUtc) {
_publishingTaskManager.Publish(page.ContentItem, scheduledPublishUtc);
}
public void Unpublish(Page page) {
_contentManager.Unpublish(page.ContentItem);
}
public DateTime? GetScheduledPublishUtc(Page page) {
var task = _publishingTaskManager.GetPublishTask(page.ContentItem);
return (task == null ? null : task.ScheduledUtc);
}
}
}

View File

@@ -90,8 +90,8 @@ foreach (var pageEntry in Model.PageEntries)
<td>
<% if (!pageEntry.Page.IsPublished)
{ %>
<%=pageEntry.Page.Published != null
? string.Format("{0:d}<br />{0:t}", pageEntry.Page.Published.Value)
<%=pageEntry.Page.ScheduledPublishUtc != null
? string.Format("{0:d}<br />{0:t}", pageEntry.Page.ScheduledPublishUtc.Value)
: ""%>
<% } %>
</td>

View File

@@ -2,7 +2,7 @@
<fieldset>
<legend><%=_Encoded("Publish Settings")%></legend>
<div>
<%=Html.RadioButton("Command", "SaveDraft", Model.ContentItem.VersionRecord == null || !Model.ContentItem.VersionRecord.Published, new { id = "Command_SaveDraft" }) %>
<%=Html.RadioButton("Command", "SaveDraft", Model.ContentItem.VersionRecord == null || !Model.ContentItem.VersionRecord.Published, new { id = "Command_SaveDraft" })%>
<label class="forcheckbox" for="Command_SaveDraft"><%=_Encoded("Save Draft")%></label>
</div>
<div>
@@ -10,8 +10,8 @@
<label class="forcheckbox" for="Command_PublishNow"><%=_Encoded("Publish Now")%></label>
</div>
<div>
<%=Html.RadioButton("Command", "PublishLater", Model.Published != null && Model.Published.Value > DateTime.UtcNow, new { id = "Command_PublishLater" }) %>
<%=Html.RadioButton("Command", "PublishLater", Model.ScheduledPublishUtc != null, new { id = "Command_PublishLater" }) %>
<label class="forcheckbox" for="Command_PublishLater"><%=_Encoded("Publish Later")%></label>
<%=Html.EditorFor(m => m.Published) %>
<%=Html.EditorFor(m => m.ScheduledPublishUtc)%>
</div>
</fieldset>

View File

@@ -140,6 +140,7 @@
<Compile Include="Security\IAuthorizationServiceEvents.cs" />
<Compile Include="Security\StandardPermissions.cs" />
<Compile Include="Security\OrchardSecurityException.cs" />
<Compile Include="Tasks\Scheduling\IPublishingTaskManager.cs" />
<Compile Include="Tasks\Scheduling\IScheduledTask.cs" />
<Compile Include="ContentManagement\ContentExtensions.cs" />
<Compile Include="ContentManagement\ContentItem.cs" />

View File

@@ -0,0 +1,10 @@
using System;
using Orchard.ContentManagement;
namespace Orchard.Tasks.Scheduling {
public interface IPublishingTaskManager : IDependency {
IScheduledTask GetPublishTask(ContentItem item);
void Publish(ContentItem item, DateTime scheduledUtc);
void DeleteTasks(ContentItem item);
}
}

View File

@@ -1,9 +1,10 @@
using System;
using Orchard.ContentManagement;
namespace Orchard.ContentManagement.Aspects {
namespace Orchard.Tasks.Scheduling {
public interface IScheduledTask {
string TaskType { get; }
DateTime? ScheduledUtc { get; }
ContentItem ContentItem { get; }
}
}
}

View File

@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
namespace Orchard.Tasks.Scheduling {
public interface IScheduledTaskManager : IDependency {
void CreateTask(string taskType, DateTime scheduledUtc, ContentItem contentItem);
IEnumerable<IScheduledTask> GetTasks(ContentItem contentItem);
void DeleteTasks(ContentItem contentItem, Func<IScheduledTask, bool> predicate);
}
}

View File

@@ -1,5 +1,3 @@
using Orchard.ContentManagement.Aspects;
namespace Orchard.Tasks.Scheduling {
public class ScheduledTaskContext {
public IScheduledTask Task { get; set; }

View File

@@ -14,7 +14,7 @@ namespace Orchard.Tasks {
_timer = new Timer();
_timer.Elapsed += Elapsed;
Logger = NullLogger.Instance;
Interval = TimeSpan.FromMinutes(5);
Interval = TimeSpan.FromSeconds(30);
}
public ILogger Logger { get; set; }