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="Properties\AssemblyInfo.cs" />
<Compile Include="Scheduling\Records\ScheduledTaskRecord.cs" /> <Compile Include="Scheduling\Records\ScheduledTaskRecord.cs" />
<Compile Include="Scheduling\Services\PublishingTaskHandler.cs" /> <Compile Include="Scheduling\Services\PublishingTaskHandler.cs" />
<Compile Include="Scheduling\Services\PublishingTaskManager.cs" />
<Compile Include="Scheduling\Services\ScheduledTaskManager.cs" /> <Compile Include="Scheduling\Services\ScheduledTaskManager.cs" />
<Compile Include="Scheduling\Services\ScheduledTaskExecutor.cs" /> <Compile Include="Scheduling\Services\ScheduledTaskExecutor.cs" />
<Compile Include="Scheduling\Models\Task.cs" /> <Compile Include="Scheduling\Models\Task.cs" />

View File

@@ -1,7 +1,7 @@
using System; using System;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Scheduling.Records; using Orchard.Core.Scheduling.Records;
using Orchard.Tasks.Scheduling;
namespace Orchard.Core.Scheduling.Models { namespace Orchard.Core.Scheduling.Models {
public class Task : IScheduledTask { 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Web;
using JetBrains.Annotations;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Scheduling.Models; using Orchard.Core.Scheduling.Models;
using Orchard.Core.Scheduling.Records; using Orchard.Core.Scheduling.Records;
using Orchard.Data; using Orchard.Data;
@@ -13,7 +10,6 @@ using Orchard.Tasks.Scheduling;
using Orchard.Utility; using Orchard.Utility;
namespace Orchard.Core.Scheduling.Services { namespace Orchard.Core.Scheduling.Services {
[UsedImplicitly]
public class ScheduledTaskManager : IScheduledTaskManager { public class ScheduledTaskManager : IScheduledTaskManager {
private readonly IRepository<ScheduledTaskRecord> _repository; private readonly IRepository<ScheduledTaskRecord> _repository;
@@ -46,5 +42,17 @@ namespace Orchard.Core.Scheduling.Services {
.Cast<IScheduledTask>() .Cast<IScheduledTask>()
.ToReadOnlyCollection(); .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"))) if (!Services.Authorizer.Authorize(Permissions.EditPages, T("Couldn't create page")))
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
//TODO: (erikpo) Move this duplicate code somewhere else // Validate form input
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;
}
}
var page = Services.ContentManager.New<Page>("page"); var page = Services.ContentManager.New<Page>("page");
model.Page = Services.ContentManager.UpdateEditorModel(page, this); model.Page = Services.ContentManager.UpdateEditorModel(page, this);
if (!publishNow && publishDate != null)
model.Page.Item.Published = publishDate.Value;
if (!ModelState.IsValid) { if (!ModelState.IsValid) {
Services.TransactionManager.Cancel(); Services.TransactionManager.Cancel();
return View(model); return View(model);
@@ -145,15 +130,20 @@ namespace Orchard.Pages.Controllers {
Services.ContentManager.Create(model.Page.Item.ContentItem, VersionOptions.Draft); Services.ContentManager.Create(model.Page.Item.ContentItem, VersionOptions.Draft);
if (publishNow) // Execute publish command
Services.ContentManager.Publish(model.Page.Item.ContentItem); switch (Request.Form["Command"]) {
case "PublishNow":
if (publishNow) _pageService.Publish(model.Page.Item);
Services.Notifier.Information(T("Page has been published")); Services.Notifier.Information(T("Page has been published"));
else if (publishDate != null) break;
Services.Notifier.Information(T("Page has been scheduled for publishing")); case "PublishLater":
else _pageService.Publish(model.Page.Item, model.Page.Item.ScheduledPublishUtc.Value);
Services.Notifier.Information(T("Page draft has been saved")); 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 }); return RedirectToAction("Edit", "Admin", new { id = model.Page.Item.ContentItem.Id });
} }
@@ -191,37 +181,24 @@ namespace Orchard.Pages.Controllers {
if (!ModelState.IsValid) { if (!ModelState.IsValid) {
Services.TransactionManager.Cancel(); Services.TransactionManager.Cancel();
return View(model); return View(model);
} }
//TODO: (erikpo) Move this duplicate code somewhere else // Execute publish command
DateTime? publishDate = null; switch (Request.Form["Command"]) {
bool publishNow = false; case "PublishNow":
if (string.Equals(Request.Form["Command"], "PublishNow")) { _pageService.Publish(model.Page.Item);
publishNow = true; 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 }); return RedirectToAction("Edit", "Admin", new { id = model.Page.Item.ContentItem.Id });
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,15 +5,16 @@ using Orchard.Pages.Models;
using Orchard.Core.Common.Records; using Orchard.Core.Common.Records;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.Services; using Orchard.Services;
using Orchard.Tasks.Scheduling;
namespace Orchard.Pages.Services { namespace Orchard.Pages.Services {
public class PageService : IPageService { public class PageService : IPageService {
private readonly IContentManager _contentManager; 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; _contentManager = contentManager;
_clock = clock; _publishingTaskManager = publishingTaskManager;
} }
public IEnumerable<Page> Get() { public IEnumerable<Page> Get() {
@@ -71,21 +72,26 @@ namespace Orchard.Pages.Services {
} }
public void Delete(Page page) { public void Delete(Page page) {
_publishingTaskManager.DeleteTasks(page.ContentItem);
_contentManager.Remove(page.ContentItem); _contentManager.Remove(page.ContentItem);
} }
public void Publish(Page page) { public void Publish(Page page) {
_publishingTaskManager.DeleteTasks(page.ContentItem);
_contentManager.Publish(page.ContentItem); _contentManager.Publish(page.ContentItem);
} }
public void Publish(Page page, DateTime publishDate) { public void Publish(Page page, DateTime scheduledPublishUtc) {
//TODO: Implement task scheduling _publishingTaskManager.Publish(page.ContentItem, scheduledPublishUtc);
//if (page.Published != null && page.Published.Value >= _clock.UtcNow)
// _contentManager.Unpublish(page.ContentItem);
} }
public void Unpublish(Page page) { public void Unpublish(Page page) {
_contentManager.Unpublish(page.ContentItem); _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> <td>
<% if (!pageEntry.Page.IsPublished) <% if (!pageEntry.Page.IsPublished)
{ %> { %>
<%=pageEntry.Page.Published != null <%=pageEntry.Page.ScheduledPublishUtc != null
? string.Format("{0:d}<br />{0:t}", pageEntry.Page.Published.Value) ? string.Format("{0:d}<br />{0:t}", pageEntry.Page.ScheduledPublishUtc.Value)
: ""%> : ""%>
<% } %> <% } %>
</td> </td>

View File

@@ -2,7 +2,7 @@
<fieldset> <fieldset>
<legend><%=_Encoded("Publish Settings")%></legend> <legend><%=_Encoded("Publish Settings")%></legend>
<div> <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> <label class="forcheckbox" for="Command_SaveDraft"><%=_Encoded("Save Draft")%></label>
</div> </div>
<div> <div>
@@ -10,8 +10,8 @@
<label class="forcheckbox" for="Command_PublishNow"><%=_Encoded("Publish Now")%></label> <label class="forcheckbox" for="Command_PublishNow"><%=_Encoded("Publish Now")%></label>
</div> </div>
<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> <label class="forcheckbox" for="Command_PublishLater"><%=_Encoded("Publish Later")%></label>
<%=Html.EditorFor(m => m.Published) %> <%=Html.EditorFor(m => m.ScheduledPublishUtc)%>
</div> </div>
</fieldset> </fieldset>

View File

@@ -140,6 +140,7 @@
<Compile Include="Security\IAuthorizationServiceEvents.cs" /> <Compile Include="Security\IAuthorizationServiceEvents.cs" />
<Compile Include="Security\StandardPermissions.cs" /> <Compile Include="Security\StandardPermissions.cs" />
<Compile Include="Security\OrchardSecurityException.cs" /> <Compile Include="Security\OrchardSecurityException.cs" />
<Compile Include="Tasks\Scheduling\IPublishingTaskManager.cs" />
<Compile Include="Tasks\Scheduling\IScheduledTask.cs" /> <Compile Include="Tasks\Scheduling\IScheduledTask.cs" />
<Compile Include="ContentManagement\ContentExtensions.cs" /> <Compile Include="ContentManagement\ContentExtensions.cs" />
<Compile Include="ContentManagement\ContentItem.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,6 +1,7 @@
using System; using System;
using Orchard.ContentManagement;
namespace Orchard.ContentManagement.Aspects { namespace Orchard.Tasks.Scheduling {
public interface IScheduledTask { public interface IScheduledTask {
string TaskType { get; } string TaskType { get; }
DateTime? ScheduledUtc { get; } DateTime? ScheduledUtc { get; }

View File

@@ -1,11 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
namespace Orchard.Tasks.Scheduling { namespace Orchard.Tasks.Scheduling {
public interface IScheduledTaskManager : IDependency { public interface IScheduledTaskManager : IDependency {
void CreateTask(string taskType, DateTime scheduledUtc, ContentItem contentItem); void CreateTask(string taskType, DateTime scheduledUtc, ContentItem contentItem);
IEnumerable<IScheduledTask> GetTasks(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 { namespace Orchard.Tasks.Scheduling {
public class ScheduledTaskContext { public class ScheduledTaskContext {
public IScheduledTask Task { get; set; } public IScheduledTask Task { get; set; }

View File

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