diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs
index fc5382499f..0971b2047a 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs
@@ -12,6 +12,11 @@ namespace Umbraco.Core.Persistence.Repositories
///
void ClearSchedule(DateTime date);
+ void ClearSchedule(DateTime date, ContentScheduleAction action);
+
+ bool HasContentForExpiration(DateTime date);
+ bool HasContentForRelease(DateTime date);
+
///
/// Gets objects having an expiration date before (lower than, or equal to) a specified date.
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
index ccfa8209fb..a34aadd70f 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -1000,6 +1000,37 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.Execute(sql);
}
+ ///
+ public void ClearSchedule(DateTime date, ContentScheduleAction action)
+ {
+ var a = action.ToString();
+ var sql = Sql().Delete().Where(x => x.Date <= date && x.Action == a);
+ Database.Execute(sql);
+ }
+
+ private Sql GetSqlForHasScheduling(ContentScheduleAction action, DateTime date)
+ {
+ var template = SqlContext.Templates.Get("Umbraco.Core.DocumentRepository.GetSqlForHasScheduling", tsql => tsql
+ .SelectCount()
+ .From()
+ .Where(x => x.Action == SqlTemplate.Arg("action") && x.Date <= SqlTemplate.Arg("date")));
+
+ var sql = template.Sql(action.ToString(), date);
+ return sql;
+ }
+
+ public bool HasContentForExpiration(DateTime date)
+ {
+ var sql = GetSqlForHasScheduling(ContentScheduleAction.Expire, date);
+ return Database.ExecuteScalar(sql) > 0;
+ }
+
+ public bool HasContentForRelease(DateTime date)
+ {
+ var sql = GetSqlForHasScheduling(ContentScheduleAction.Release, date);
+ return Database.ExecuteScalar(sql) > 0;
+ }
+
///
public IEnumerable GetContentForRelease(DateTime date)
{
diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs
index 93e7f0a5df..2a23a1adad 100644
--- a/src/Umbraco.Core/Services/Implement/ContentService.cs
+++ b/src/Umbraco.Core/Services/Implement/ContentService.cs
@@ -30,7 +30,7 @@ namespace Umbraco.Core.Services.Implement
private IQuery _queryNotTrashed;
//TODO: The non-lazy object should be injected
private readonly Lazy _propertyValidationService = new Lazy(() => new PropertyValidationService());
-
+
#region Constructors
@@ -875,7 +875,7 @@ namespace Umbraco.Core.Services.Implement
throw new NotSupportedException($"Culture \"{culture}\" is not supported by invariant content types.");
}
- if(content.Name != null && content.Name.Length > 255)
+ if (content.Name != null && content.Name.Length > 255)
{
throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
}
@@ -1243,7 +1243,7 @@ namespace Umbraco.Core.Services.Implement
if (culturesUnpublishing != null)
{
// This will mean that that we unpublished a mandatory culture or we unpublished the last culture.
-
+
var langs = string.Join(", ", allLangs
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
@@ -1252,7 +1252,7 @@ namespace Umbraco.Core.Services.Implement
if (publishResult == null)
throw new PanicException("publishResult == null - should not happen");
- switch(publishResult.Result)
+ switch (publishResult.Result)
{
case PublishResultType.FailedPublishMandatoryCultureMissing:
//occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture)
@@ -1266,7 +1266,7 @@ namespace Umbraco.Core.Services.Implement
Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (last language unpublished)");
return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, evtMsgs, content);
}
-
+
}
Audit(AuditType.Unpublish, userId, content.Id);
@@ -1286,7 +1286,7 @@ namespace Umbraco.Core.Services.Implement
changeType = TreeChangeTypes.RefreshBranch; // whole branch
else if (isNew == false && previouslyPublished)
changeType = TreeChangeTypes.RefreshNode; // single node
-
+
// invalidate the node/branch
if (!branchOne) // for branches, handled by SaveAndPublishBranch
@@ -1359,17 +1359,88 @@ namespace Umbraco.Core.Services.Implement
}
///
- public IEnumerable PerformScheduledPublish(DateTime date)
+ public IEnumerable PerformScheduledPublish(DateTime date)
{
+ var allLangs = new Lazy>(() => _languageRepository.GetMany().ToList());
var evtMsgs = EventMessagesFactory.Get();
-
var results = new List();
- using (var scope = ScopeProvider.CreateScope())
+ PerformScheduledPublishingRelease(date, results, evtMsgs, allLangs);
+ PerformScheduledPublishingExpiration(date, results, evtMsgs, allLangs);
+
+ return results;
+ }
+
+ private void PerformScheduledPublishingExpiration(DateTime date, List results, EventMessages evtMsgs, Lazy> allLangs)
+ {
+ using var scope = ScopeProvider.CreateScope();
+
+ // do a fast read without any locks since this executes often to see if we even need to proceed
+ if (_documentRepository.HasContentForExpiration(date))
{
+ // now take a write lock since we'll be updating
scope.WriteLock(Constants.Locks.ContentTree);
- var allLangs = _languageRepository.GetMany().ToList();
+ foreach (var d in _documentRepository.GetContentForExpiration(date))
+ {
+ if (d.ContentType.VariesByCulture())
+ {
+ //find which cultures have pending schedules
+ var pendingCultures = d.ContentSchedule.GetPending(ContentScheduleAction.Expire, date)
+ .Select(x => x.Culture)
+ .Distinct()
+ .ToList();
+
+ if (pendingCultures.Count == 0)
+ continue; //shouldn't happen but no point in continuing if there's nothing there
+
+ var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs);
+ if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
+ {
+ results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d));
+ continue;
+ }
+
+ foreach (var c in pendingCultures)
+ {
+ //Clear this schedule for this culture
+ d.ContentSchedule.Clear(c, ContentScheduleAction.Expire, date);
+ //set the culture to be published
+ d.UnpublishCulture(c);
+ }
+
+ var result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs.Value, d.WriterId);
+ if (result.Success == false)
+ Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
+ results.Add(result);
+
+ }
+ else
+ {
+ //Clear this schedule
+ d.ContentSchedule.Clear(ContentScheduleAction.Expire, date);
+ var result = Unpublish(d, userId: d.WriterId);
+ if (result.Success == false)
+ Logger.Error(null, "Failed to unpublish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
+ results.Add(result);
+ }
+ }
+
+ _documentRepository.ClearSchedule(date, ContentScheduleAction.Expire);
+ }
+
+ scope.Complete();
+ }
+
+ private void PerformScheduledPublishingRelease(DateTime date, List results, EventMessages evtMsgs, Lazy> allLangs)
+ {
+ using var scope = ScopeProvider.CreateScope();
+
+ // do a fast read without any locks since this executes often to see if we even need to proceed
+ if (_documentRepository.HasContentForRelease(date))
+ {
+ // now take a write lock since we'll be updating
+ scope.WriteLock(Constants.Locks.ContentTree);
foreach (var d in _documentRepository.GetContentForRelease(date))
{
@@ -1382,13 +1453,13 @@ namespace Umbraco.Core.Services.Implement
.ToList();
if (pendingCultures.Count == 0)
- break; //shouldn't happen but no point in continuing if there's nothing there
+ continue; //shouldn't happen but no point in continuing if there's nothing there
var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
{
results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d));
- continue;
+ continue; // this document is canceled move next
}
var publishing = true;
@@ -1401,14 +1472,14 @@ namespace Umbraco.Core.Services.Implement
//publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed
Property[] invalidProperties = null;
- var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs, culture));
+ var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs.Value, culture));
var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact);
if (invalidProperties != null && invalidProperties.Length > 0)
Logger.Warn("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}",
d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias)));
publishing &= tryPublish; //set the culture to be published
- if (!publishing) break; // no point continuing
+ if (!publishing) continue; // no point continuing
}
PublishResult result;
@@ -1418,7 +1489,7 @@ namespace Umbraco.Core.Services.Implement
else if (!publishing)
result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d);
else
- result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId);
+ result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs.Value, d.WriterId);
if (result.Success == false)
Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
@@ -1441,60 +1512,11 @@ namespace Umbraco.Core.Services.Implement
}
}
- foreach (var d in _documentRepository.GetContentForExpiration(date))
- {
-
- if (d.ContentType.VariesByCulture())
- {
- //find which cultures have pending schedules
- var pendingCultures = d.ContentSchedule.GetPending(ContentScheduleAction.Expire, date)
- .Select(x => x.Culture)
- .Distinct()
- .ToList();
+ _documentRepository.ClearSchedule(date, ContentScheduleAction.Release);
- if (pendingCultures.Count == 0)
- break; //shouldn't happen but no point in continuing if there's nothing there
-
- var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs);
- if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
- {
- results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d));
- continue;
- }
-
- foreach (var c in pendingCultures)
- {
- //Clear this schedule for this culture
- d.ContentSchedule.Clear(c, ContentScheduleAction.Expire, date);
- //set the culture to be published
- d.UnpublishCulture(c);
- }
-
- var result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId);
- if (result.Success == false)
- Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
- results.Add(result);
-
- }
- else
- {
- //Clear this schedule
- d.ContentSchedule.Clear(ContentScheduleAction.Expire, date);
- var result = Unpublish(d, userId: d.WriterId);
- if (result.Success == false)
- Logger.Error(null, "Failed to unpublish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
- results.Add(result);
- }
-
-
- }
-
- _documentRepository.ClearSchedule(date);
-
- scope.Complete();
}
- return results;
+ scope.Complete();
}
// utility 'PublishCultures' func used by SaveAndPublishBranch
@@ -2627,7 +2649,7 @@ namespace Umbraco.Core.Services.Implement
// there will be nothing to publish/unpublish.
return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
}
-
+
// missing mandatory culture = cannot be published
var mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode);
@@ -3140,6 +3162,6 @@ namespace Umbraco.Core.Services.Implement
#endregion
-
+
}
}