Fixes: U4-581 Automatic publishing not working in load balanced setup - added some more convention and configuration to distributed calls so that servers are aware of the master and how to call into themselves for scheduled tasks, ping and scheduled publishing. Will need to update the docs on LB regarding this too. Cleaned up the code that does the scheduling and separates it into proper segments. Obsoletes the old presentation classes that were doing it.
This commit is contained in:
61
src/Umbraco.Core/Publishing/ScheduledPublisher.cs
Normal file
61
src/Umbraco.Core/Publishing/ScheduledPublisher.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Publishing
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to perform scheduled publishing/unpublishing
|
||||
/// </summary>
|
||||
internal class ScheduledPublisher
|
||||
{
|
||||
private readonly IContentService _contentService;
|
||||
|
||||
public ScheduledPublisher(IContentService contentService)
|
||||
{
|
||||
_contentService = contentService;
|
||||
}
|
||||
|
||||
public void CheckPendingAndProcess()
|
||||
{
|
||||
foreach (var d in _contentService.GetContentForRelease())
|
||||
{
|
||||
try
|
||||
{
|
||||
d.ReleaseDate = null;
|
||||
var result = _contentService.SaveAndPublishWithStatus(d, (int)d.GetWriterProfile().Id);
|
||||
if (result.Success == false)
|
||||
{
|
||||
if (result.Exception != null)
|
||||
{
|
||||
LogHelper.Error<ScheduledPublisher>("Could not published the document (" + d.Id + ") based on it's scheduled release, status result: " + result.Result.StatusType, result.Exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Warn<ScheduledPublisher>("Could not published the document (" + d.Id + ") based on it's scheduled release. Status result: " + result.Result.StatusType);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
LogHelper.Error<ScheduledPublisher>(string.Format("Error publishing node {0}", d.Id), ee);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
foreach (var d in _contentService.GetContentForExpiration())
|
||||
{
|
||||
try
|
||||
{
|
||||
d.ExpireDate = null;
|
||||
_contentService.UnPublish(d, (int)d.GetWriterProfile().Id);
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
LogHelper.Error<ScheduledPublisher>(string.Format("Error unpublishing node {0}", d.Id), ee);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/Umbraco.Core/Sync/CurrentServerEnvironmentStatus.cs
Normal file
28
src/Umbraco.Core/Sync/CurrentServerEnvironmentStatus.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
/// <summary>
|
||||
/// The current status of the server in the Umbraco environment
|
||||
/// </summary>
|
||||
internal enum CurrentServerEnvironmentStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// If the current server is detected as the 'master' server when configured in a load balanced scenario
|
||||
/// </summary>
|
||||
Master,
|
||||
|
||||
/// <summary>
|
||||
/// If the current server is detected as a 'slave' server when configured in a load balanced scenario
|
||||
/// </summary>
|
||||
Slave,
|
||||
|
||||
/// <summary>
|
||||
/// If the current server cannot be detected as a 'slave' or 'master' when configured in a load balanced scenario
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// If load balancing is not enabled and this is the only server in the umbraco environment
|
||||
/// </summary>
|
||||
Single
|
||||
}
|
||||
}
|
||||
116
src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs
Normal file
116
src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Xml;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper used to determine the current server environment status
|
||||
/// </summary>
|
||||
internal static class ServerEnvironmentHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the current umbraco base url for the current server depending on it's environment
|
||||
/// status. This will attempt to determine the internal umbraco base url that can be used by the current
|
||||
/// server to send a request to itself if it is in a load balanced environment.
|
||||
/// </summary>
|
||||
/// <returns>The full base url including schema (i.e. http://myserver:80/umbraco )</returns>
|
||||
public static string GetCurrentServerUmbracoBaseUrl()
|
||||
{
|
||||
var status = GetStatus();
|
||||
|
||||
if (status == CurrentServerEnvironmentStatus.Single)
|
||||
{
|
||||
//if it's a single install, then the base url has to be the first url registered
|
||||
return ApplicationContext.Current.OriginalRequestUrl;
|
||||
}
|
||||
|
||||
var servers = UmbracoSettings.DistributionServers;
|
||||
|
||||
var nodes = servers.SelectNodes("./server");
|
||||
if (nodes == null)
|
||||
{
|
||||
//cannot be determined, then the base url has to be the first url registered
|
||||
return ApplicationContext.Current.OriginalRequestUrl;
|
||||
}
|
||||
|
||||
var xmlNodes = nodes.Cast<XmlNode>().ToList();
|
||||
|
||||
foreach (var xmlNode in xmlNodes)
|
||||
{
|
||||
var appId = xmlNode.AttributeValue<string>("appId");
|
||||
var serverName = xmlNode.AttributeValue<string>("serverName");
|
||||
|
||||
if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
|
||||
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
|
||||
{
|
||||
//match by appId or computer name! return the url configured
|
||||
return string.Format("{0}://{1}:{2}/{3}",
|
||||
xmlNode.AttributeValue<string>("forceProtocol").IsNullOrWhiteSpace() ? "http" : xmlNode.AttributeValue<string>("forceProtocol"),
|
||||
xmlNode.InnerText,
|
||||
xmlNode.AttributeValue<string>("forcePortnumber").IsNullOrWhiteSpace() ? "80" : xmlNode.AttributeValue<string>("forcePortnumber"),
|
||||
IOHelper.ResolveUrl(SystemDirectories.Umbraco).TrimStart('/'));
|
||||
}
|
||||
}
|
||||
|
||||
//cannot be determined, then the base url has to be the first url registered
|
||||
return ApplicationContext.Current.OriginalRequestUrl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current environment status for the current server
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static CurrentServerEnvironmentStatus GetStatus()
|
||||
{
|
||||
if (UmbracoSettings.UseDistributedCalls == false)
|
||||
{
|
||||
return CurrentServerEnvironmentStatus.Single;
|
||||
}
|
||||
|
||||
var servers = UmbracoSettings.DistributionServers;
|
||||
|
||||
var nodes = servers.SelectNodes("./server");
|
||||
if (nodes == null)
|
||||
{
|
||||
return CurrentServerEnvironmentStatus.Unknown;
|
||||
}
|
||||
|
||||
var master = nodes.Cast<XmlNode>().FirstOrDefault();
|
||||
|
||||
if (master == null)
|
||||
{
|
||||
return CurrentServerEnvironmentStatus.Unknown;
|
||||
}
|
||||
|
||||
//we determine master/slave based on the first server registered
|
||||
//TODO: In v7 we have publicized ServerRegisterResolver - we won't be able to determine this based on that
|
||||
// but we'd need to change the IServerAddress interfaces which is breaking.
|
||||
|
||||
var appId = master.AttributeValue<string>("appId");
|
||||
var serverName = master.AttributeValue<string>("serverName");
|
||||
|
||||
if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace())
|
||||
{
|
||||
return CurrentServerEnvironmentStatus.Unknown;
|
||||
}
|
||||
|
||||
if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
|
||||
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
|
||||
{
|
||||
//match by appdid or server name!
|
||||
return CurrentServerEnvironmentStatus.Master;
|
||||
}
|
||||
|
||||
return CurrentServerEnvironmentStatus.Slave;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -770,6 +770,7 @@
|
||||
<Compile Include="Publishing\PublishingStrategy.cs" />
|
||||
<Compile Include="Publishing\PublishStatus.cs" />
|
||||
<Compile Include="Publishing\PublishStatusType.cs" />
|
||||
<Compile Include="Publishing\ScheduledPublisher.cs" />
|
||||
<Compile Include="RenderingEngine.cs" />
|
||||
<Compile Include="Security\AuthenticationExtensions.cs" />
|
||||
<Compile Include="Security\IUsersMembershipProvider.cs" />
|
||||
@@ -830,7 +831,9 @@
|
||||
<Compile Include="Standalone\StandaloneCoreBootManager.cs" />
|
||||
<Compile Include="Strings\ContentBaseExtensions.cs" />
|
||||
<Compile Include="Strings\Diff.cs" />
|
||||
<Compile Include="Sync\CurrentServerEnvironmentStatus.cs" />
|
||||
<Compile Include="Sync\RefreshInstruction.cs" />
|
||||
<Compile Include="Sync\ServerEnvironmentHelper.cs" />
|
||||
<Compile Include="TopologicalSorter.cs" />
|
||||
<Compile Include="Strings\DefaultUrlSegmentProvider.cs" />
|
||||
<Compile Include="Strings\IUrlSegmentProvider.cs" />
|
||||
|
||||
@@ -9,6 +9,7 @@ using Umbraco.Core;
|
||||
using Umbraco.Tests.PartialTrust;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.Cache;
|
||||
using Umbraco.Web.Scheduling;
|
||||
using UmbracoExamine;
|
||||
using umbraco;
|
||||
using umbraco.presentation;
|
||||
@@ -67,7 +68,7 @@ namespace Umbraco.Tests
|
||||
Assert.AreEqual(typeof(UmbracoEventManager), t5.Result);
|
||||
|
||||
var t6 = TypeHelper.GetLowestBaseType(typeof (IApplicationEventHandler),
|
||||
typeof (LegacyScheduledTasks),
|
||||
typeof (Scheduler),
|
||||
typeof(CacheRefresherEventHandler));
|
||||
Assert.IsTrue(t6.Success);
|
||||
Assert.AreEqual(typeof(IApplicationEventHandler), t6.Result);
|
||||
|
||||
@@ -223,16 +223,47 @@
|
||||
<!-- <task log="true" alias="test60" interval="60" url="http://localhost/umbraco/test.aspx"/>-->
|
||||
</scheduledTasks>
|
||||
|
||||
<!-- distributed calls make umbraco use webservices to handle cache refreshing -->
|
||||
<!-- distributed calls must be enabled when using Umbraco in a load balanced environment -->
|
||||
<distributedCall enable="false">
|
||||
<!-- the id of the user who's making the calls -->
|
||||
<!-- needed for security, umbraco will automatically look up correct login and passwords -->
|
||||
<user>0</user>
|
||||
|
||||
<!--
|
||||
When distributed call is enabled, you need to add all of the servers part taking in load balancing
|
||||
to the server list below.
|
||||
-->
|
||||
|
||||
<servers>
|
||||
<!-- add ip number or hostname, make sure that it can be reached from all servers -->
|
||||
<!-- you can also add optional attributes to force a protocol or port number (see #2) -->
|
||||
<!-- <server>127.0.0.1</server>-->
|
||||
<!-- <server forceProtocol="http|https" forcePortnumber="80|443">127.0.0.1</server>-->
|
||||
|
||||
<!--
|
||||
Add ip number or hostname, make sure that it can be reached from all servers
|
||||
you can also add optional attributes to force a protocol or port number.
|
||||
|
||||
Examples:
|
||||
|
||||
<server>127.0.0.1</server>
|
||||
<server forceProtocol="http|https" forcePortnumber="80|443">127.0.0.1</server>
|
||||
|
||||
Generally when setting up load balancing you will designate a 'master' server,
|
||||
Umbraco will always assume that the FIRST server listed in this list is the 'master'.
|
||||
(NOTE: Not all load balancing scenarios have a 'master', depends on how you are setting it up)
|
||||
|
||||
In order for scheduled tasks (including scheduled publishing) to work properly when load balancing, each
|
||||
server in the load balanced environment needs to know if it is the 'master'. In order for servers
|
||||
to know this or not, they need to compare some values against the servers listed. These values
|
||||
are either: serverName or appId. You should not enter both values but appId will always supersede serverName.
|
||||
The serverName is the easiest and will work so long as you are not load balancing your site on the same server.
|
||||
If you are doing this, then you will need to use appId which is equivalent to the value returned from
|
||||
HttpRuntime.AppDomainAppId. It is recommended that you set either the serverName or appId for all servers
|
||||
registered here if possible, not just the first one.
|
||||
|
||||
Examples:
|
||||
|
||||
<server serverName="MyServer">server1.mysite.com</server>
|
||||
<server appId="/LM/W3SVC/69/ROOT">server1.mysite.com</server>
|
||||
-->
|
||||
|
||||
</servers>
|
||||
</distributedCall>
|
||||
|
||||
|
||||
@@ -223,16 +223,51 @@
|
||||
<!-- <task log="true" alias="test60" interval="60" url="http://localhost/umbraco/test.aspx"/>-->
|
||||
</scheduledTasks>
|
||||
|
||||
<!-- distributed calls make umbraco use webservices to handle cache refreshing -->
|
||||
<distributedCall enable="false">
|
||||
<!-- distributed calls must be enabled when using Umbraco in a load balanced environment -->
|
||||
<distributedCall enable="true">
|
||||
<!-- the id of the user who's making the calls -->
|
||||
<!-- needed for security, umbraco will automatically look up correct login and passwords -->
|
||||
<user>0</user>
|
||||
|
||||
<!--
|
||||
When distributed call is enabled, you need to add all of the servers part taking in load balancing
|
||||
to the server list below.
|
||||
-->
|
||||
|
||||
<servers>
|
||||
<!-- add ip number or hostname, make sure that it can be reached from all servers -->
|
||||
<!-- you can also add optional attributes to force a protocol or port number (see #2) -->
|
||||
<!-- <server>127.0.0.1</server>-->
|
||||
<!-- <server forceProtocol="http|https" forcePortnumber="80|443">127.0.0.1</server>-->
|
||||
|
||||
<!--
|
||||
Add ip number or hostname, make sure that it can be reached from all servers
|
||||
you can also add optional attributes to force a protocol or port number.
|
||||
|
||||
Examples:
|
||||
|
||||
<server>127.0.0.1</server>
|
||||
<server forceProtocol="http|https" forcePortnumber="80|443">127.0.0.1</server>
|
||||
|
||||
Generally when setting up load balancing you will designate a 'master' server,
|
||||
Umbraco will always assume that the FIRST server listed in this list is the 'master'.
|
||||
(NOTE: Not all load balancing scenarios have a 'master', depends on how you are setting it up)
|
||||
|
||||
In order for scheduled tasks (including scheduled publishing) to work properly when load balancing, each
|
||||
server in the load balanced environment needs to know if it is the 'master'. In order for servers
|
||||
to know this or not, they need to compare some values against the servers listed. These values
|
||||
are either: serverName or appId. You should not enter both values but appId will always supersede serverName.
|
||||
The serverName is the easiest and will work so long as you are not load balancing your site on the same server.
|
||||
If you are doing this, then you will need to use appId which is equivalent to the value returned from
|
||||
HttpRuntime.AppDomainAppId. It is recommended that you set either the serverName or appId for all servers
|
||||
registered here if possible, not just the first one.
|
||||
|
||||
Examples:
|
||||
|
||||
<server serverName="MyServer">server1.mysite.com</server>
|
||||
<server appId="/LM/W3SVC/69/ROOT">server1.mysite.com</server>
|
||||
-->
|
||||
|
||||
<server forcePortnumber="6210" appId="/LM/W3SVC/69/ROOT">localhost</server>
|
||||
<server>umb1.dev</server>
|
||||
<server>umb2.dev</server>
|
||||
|
||||
</servers>
|
||||
</distributedCall>
|
||||
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Web.Caching;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using global::umbraco.BusinessLogic;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
// note: has to be public to be detected by the resolver
|
||||
// if it's made internal, which would make more sense, then it's not detected
|
||||
// and it needs to be manually registered - which we want to avoid, in order
|
||||
// to be as unobtrusive as possible
|
||||
|
||||
internal sealed class LegacyScheduledTasks : ApplicationEventHandler
|
||||
{
|
||||
Timer _pingTimer;
|
||||
Timer _publishingTimer;
|
||||
CacheItemRemovedCallback _onCacheRemove;
|
||||
|
||||
protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, Core.ApplicationContext applicationContext)
|
||||
{
|
||||
if (umbracoApplication.Context == null)
|
||||
return;
|
||||
|
||||
// time to setup the tasks
|
||||
|
||||
// these are the legacy tasks
|
||||
// just copied over here for backward compatibility
|
||||
// of course we should have a proper scheduler, see #U4-809
|
||||
|
||||
// ping/keepalive
|
||||
_pingTimer = new Timer(new TimerCallback(global::umbraco.presentation.keepAliveService.PingUmbraco), applicationContext, 60000, 300000);
|
||||
|
||||
// (un)publishing _and_ also run scheduled tasks (!)
|
||||
_publishingTimer = new Timer(new TimerCallback(global::umbraco.presentation.publishingService.CheckPublishing), applicationContext, 30000, 60000);
|
||||
|
||||
// log scrubbing
|
||||
AddTask(LOG_SCRUBBER_TASK_NAME, GetLogScrubbingInterval());
|
||||
}
|
||||
|
||||
#region Log Scrubbing
|
||||
|
||||
// this is a raw copy of the legacy code in all its uglyness
|
||||
|
||||
const string LOG_SCRUBBER_TASK_NAME = "ScrubLogs";
|
||||
|
||||
private static int GetLogScrubbingInterval()
|
||||
{
|
||||
int interval = 24 * 60 * 60; //24 hours
|
||||
try
|
||||
{
|
||||
if (global::umbraco.UmbracoSettings.CleaningMiliseconds > -1)
|
||||
interval = global::umbraco.UmbracoSettings.CleaningMiliseconds;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogHelper.Error<LegacyScheduledTasks>("Unable to locate a log scrubbing interval. Defaulting to 24 horus", e);
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
private static int GetLogScrubbingMaximumAge()
|
||||
{
|
||||
int maximumAge = 24 * 60 * 60;
|
||||
try
|
||||
{
|
||||
if (global::umbraco.UmbracoSettings.MaxLogAge > -1)
|
||||
maximumAge = global::umbraco.UmbracoSettings.MaxLogAge;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogHelper.Error<LegacyScheduledTasks>("Unable to locate a log scrubbing maximum age. Defaulting to 24 horus", e);
|
||||
}
|
||||
return maximumAge;
|
||||
|
||||
}
|
||||
|
||||
private void AddTask(string name, int seconds)
|
||||
{
|
||||
_onCacheRemove = new CacheItemRemovedCallback(CacheItemRemoved);
|
||||
HttpRuntime.Cache.Insert(name, seconds, null,
|
||||
DateTime.Now.AddSeconds(seconds), System.Web.Caching.Cache.NoSlidingExpiration,
|
||||
CacheItemPriority.NotRemovable, _onCacheRemove);
|
||||
}
|
||||
|
||||
public void CacheItemRemoved(string k, object v, CacheItemRemovedReason r)
|
||||
{
|
||||
if (k.Equals(LOG_SCRUBBER_TASK_NAME))
|
||||
{
|
||||
ScrubLogs();
|
||||
}
|
||||
AddTask(k, Convert.ToInt32(v));
|
||||
}
|
||||
|
||||
private static void ScrubLogs()
|
||||
{
|
||||
Log.CleanLogs(GetLogScrubbingMaximumAge());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
35
src/Umbraco.Web/Scheduling/KeepAlive.cs
Normal file
35
src/Umbraco.Web/Scheduling/KeepAlive.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Sync;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
internal class KeepAlive
|
||||
{
|
||||
public static void Start(object sender)
|
||||
{
|
||||
//NOTE: sender will be the umbraco ApplicationContext
|
||||
|
||||
var appContext = sender as ApplicationContext;
|
||||
if (appContext == null) return;
|
||||
|
||||
var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl();
|
||||
|
||||
var url = string.Format("{0}/ping.aspx", umbracoBaseUrl);
|
||||
|
||||
try
|
||||
{
|
||||
using (var wc = new WebClient())
|
||||
{
|
||||
wc.DownloadString(url);
|
||||
}
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
LogHelper.Error<KeepAlive>("Error in ping", ee);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/Umbraco.Web/Scheduling/LogScrubber.cs
Normal file
77
src/Umbraco.Web/Scheduling/LogScrubber.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using System.Web.Caching;
|
||||
using umbraco.BusinessLogic;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
//TODO: Refactor this to use a normal scheduling processor!
|
||||
|
||||
internal class LogScrubber
|
||||
{
|
||||
// this is a raw copy of the legacy code in all its uglyness
|
||||
|
||||
CacheItemRemovedCallback _onCacheRemove;
|
||||
const string LogScrubberTaskName = "ScrubLogs";
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// log scrubbing
|
||||
AddTask(LogScrubberTaskName, GetLogScrubbingInterval());
|
||||
}
|
||||
|
||||
private static int GetLogScrubbingInterval()
|
||||
{
|
||||
int interval = 24 * 60 * 60; //24 hours
|
||||
try
|
||||
{
|
||||
if (global::umbraco.UmbracoSettings.CleaningMiliseconds > -1)
|
||||
interval = global::umbraco.UmbracoSettings.CleaningMiliseconds;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogHelper.Error<Scheduler>("Unable to locate a log scrubbing interval. Defaulting to 24 horus", e);
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
private static int GetLogScrubbingMaximumAge()
|
||||
{
|
||||
int maximumAge = 24 * 60 * 60;
|
||||
try
|
||||
{
|
||||
if (global::umbraco.UmbracoSettings.MaxLogAge > -1)
|
||||
maximumAge = global::umbraco.UmbracoSettings.MaxLogAge;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogHelper.Error<Scheduler>("Unable to locate a log scrubbing maximum age. Defaulting to 24 horus", e);
|
||||
}
|
||||
return maximumAge;
|
||||
|
||||
}
|
||||
|
||||
private void AddTask(string name, int seconds)
|
||||
{
|
||||
_onCacheRemove = new CacheItemRemovedCallback(CacheItemRemoved);
|
||||
HttpRuntime.Cache.Insert(name, seconds, null,
|
||||
DateTime.Now.AddSeconds(seconds), System.Web.Caching.Cache.NoSlidingExpiration,
|
||||
CacheItemPriority.NotRemovable, _onCacheRemove);
|
||||
}
|
||||
|
||||
public void CacheItemRemoved(string k, object v, CacheItemRemovedReason r)
|
||||
{
|
||||
if (k.Equals(LogScrubberTaskName))
|
||||
{
|
||||
ScrubLogs();
|
||||
}
|
||||
AddTask(k, Convert.ToInt32(v));
|
||||
}
|
||||
|
||||
private static void ScrubLogs()
|
||||
{
|
||||
Log.CleanLogs(GetLogScrubbingMaximumAge());
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/Umbraco.Web/Scheduling/ScheduledPublishing.cs
Normal file
47
src/Umbraco.Web/Scheduling/ScheduledPublishing.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Publishing;
|
||||
using Umbraco.Core.Sync;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
internal class ScheduledPublishing
|
||||
{
|
||||
private static bool _isPublishingRunning = false;
|
||||
|
||||
public void Start(object sender)
|
||||
{
|
||||
//NOTE: sender will be the umbraco ApplicationContext
|
||||
|
||||
var appContext = sender as ApplicationContext;
|
||||
if (appContext == null) return;
|
||||
|
||||
if (_isPublishingRunning) return;
|
||||
|
||||
_isPublishingRunning = true;
|
||||
|
||||
try
|
||||
{
|
||||
var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl();
|
||||
var url = string.Format("{0}/RestServices/ScheduledPublish/", umbracoBaseUrl);
|
||||
using (var wc = new WebClient())
|
||||
{
|
||||
var result = wc.UploadString(url, "");
|
||||
}
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
LogHelper.Error<ScheduledPublishing>("An error occurred with the scheduled publishing", ee);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isPublishingRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
98
src/Umbraco.Web/Scheduling/ScheduledTasks.cs
Normal file
98
src/Umbraco.Web/Scheduling/ScheduledTasks.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Net;
|
||||
using System.Xml;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Publishing;
|
||||
using Umbraco.Core.Sync;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
//TODO: No scheduled task (i.e. URL) would be secured, so if people are actually using these each task
|
||||
// would need to be a publicly available task (URL) which isn't really very good :(
|
||||
|
||||
internal class ScheduledTasks
|
||||
{
|
||||
private static readonly Hashtable ScheduledTaskTimes = new Hashtable();
|
||||
private static bool _isPublishingRunning = false;
|
||||
|
||||
public void Start(object sender)
|
||||
{
|
||||
//NOTE: sender will be the umbraco ApplicationContext
|
||||
|
||||
if (_isPublishingRunning) return;
|
||||
|
||||
_isPublishingRunning = true;
|
||||
|
||||
try
|
||||
{
|
||||
ProcessTasks();
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
LogHelper.Error<ScheduledTasks>("Error executing scheduled task", ee);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isPublishingRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessTasks()
|
||||
{
|
||||
|
||||
|
||||
var scheduledTasks = UmbracoSettings.ScheduledTasks;
|
||||
if (scheduledTasks != null)
|
||||
{
|
||||
var tasks = scheduledTasks.SelectNodes("./task");
|
||||
if (tasks == null) return;
|
||||
|
||||
foreach (XmlNode task in tasks)
|
||||
{
|
||||
var runTask = false;
|
||||
if (ScheduledTaskTimes.ContainsKey(task.Attributes.GetNamedItem("alias").Value) == false)
|
||||
{
|
||||
runTask = true;
|
||||
ScheduledTaskTimes.Add(task.Attributes.GetNamedItem("alias").Value, DateTime.Now);
|
||||
}
|
||||
// Add 1 second to timespan to compensate for differencies in timer
|
||||
else if (new TimeSpan(
|
||||
DateTime.Now.Ticks - ((DateTime)ScheduledTaskTimes[task.Attributes.GetNamedItem("alias").Value]).Ticks).TotalSeconds + 1
|
||||
>= int.Parse(task.Attributes.GetNamedItem("interval").Value))
|
||||
{
|
||||
runTask = true;
|
||||
ScheduledTaskTimes[task.Attributes.GetNamedItem("alias").Value] = DateTime.Now;
|
||||
}
|
||||
|
||||
if (runTask)
|
||||
{
|
||||
bool taskResult = GetTaskByHttp(task.Attributes.GetNamedItem("url").Value);
|
||||
if (bool.Parse(task.Attributes.GetNamedItem("log").Value))
|
||||
LogHelper.Info<ScheduledTasks>(string.Format("{0} has been called with response: {1}", task.Attributes.GetNamedItem("alias").Value, taskResult));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetTaskByHttp(string url)
|
||||
{
|
||||
var myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
|
||||
|
||||
try
|
||||
{
|
||||
using (var response = (HttpWebResponse)myHttpWebRequest.GetResponse())
|
||||
{
|
||||
return response.StatusCode == HttpStatusCode.OK;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<ScheduledTasks>("An error occurred calling web task for url: " + url, ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/Umbraco.Web/Scheduling/Scheduler.cs
Normal file
82
src/Umbraco.Web/Scheduling/Scheduler.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Threading;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Sync;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to do the scheduling for tasks, publishing, etc...
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
///
|
||||
/// TODO: Much of this code is legacy and needs to be updated, there are a few new/better ways to do scheduling
|
||||
/// in a web project nowadays.
|
||||
///
|
||||
/// </remarks>
|
||||
internal sealed class Scheduler : ApplicationEventHandler
|
||||
{
|
||||
private Timer _pingTimer;
|
||||
private Timer _schedulingTimer;
|
||||
private LogScrubber _scrubber;
|
||||
|
||||
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
|
||||
{
|
||||
if (umbracoApplication.Context == null)
|
||||
return;
|
||||
|
||||
// time to setup the tasks
|
||||
|
||||
// these are the legacy tasks
|
||||
// just copied over here for backward compatibility
|
||||
// of course we should have a proper scheduler, see #U4-809
|
||||
|
||||
// ping/keepalive
|
||||
_pingTimer = new Timer(KeepAlive.Start, applicationContext, 60000, 300000);
|
||||
|
||||
// scheduled publishing/unpublishing
|
||||
|
||||
_schedulingTimer = new Timer(PerformScheduling, applicationContext, 30000, 60000);
|
||||
|
||||
//log scrubbing
|
||||
_scrubber = new LogScrubber();
|
||||
_scrubber.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This performs all of the scheduling on the one timer
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <remarks>
|
||||
/// No processing will be done if this server is a slave
|
||||
/// </remarks>
|
||||
private static void PerformScheduling(object sender)
|
||||
{
|
||||
|
||||
//get the current server status to see if this server should execute the scheduled publishing
|
||||
var serverStatus = ServerEnvironmentHelper.GetStatus();
|
||||
|
||||
switch (serverStatus)
|
||||
{
|
||||
case CurrentServerEnvironmentStatus.Single:
|
||||
case CurrentServerEnvironmentStatus.Master:
|
||||
case CurrentServerEnvironmentStatus.Unknown:
|
||||
//if it's a single server install, a master or it cannot be determined
|
||||
// then we will process the scheduling
|
||||
|
||||
//do the scheduled publishing
|
||||
var scheduledPublishing = new ScheduledPublishing();
|
||||
scheduledPublishing.Start(sender);
|
||||
|
||||
//do the scheduled tasks
|
||||
var scheduledTasks = new ScheduledTasks();
|
||||
scheduledTasks.Start(sender);
|
||||
|
||||
break;
|
||||
case CurrentServerEnvironmentStatus.Slave:
|
||||
//do not process
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -325,6 +325,10 @@
|
||||
<Compile Include="Routing\ContentLastChanceFinderByNotFoundHandlers.cs" />
|
||||
<Compile Include="Models\RegisterModel.cs" />
|
||||
<Compile Include="Models\LoginModel.cs" />
|
||||
<Compile Include="Scheduling\KeepAlive.cs" />
|
||||
<Compile Include="Scheduling\LogScrubber.cs" />
|
||||
<Compile Include="Scheduling\ScheduledPublishing.cs" />
|
||||
<Compile Include="Scheduling\ScheduledTasks.cs" />
|
||||
<Compile Include="Security\MembershipHelper.cs" />
|
||||
<Compile Include="Security\Providers\MembersMembershipProvider.cs" />
|
||||
<Compile Include="Security\Providers\MembersRoleProvider.cs" />
|
||||
@@ -499,7 +503,7 @@
|
||||
<SubType>ASPXCodeBehind</SubType>
|
||||
</Compile>
|
||||
<Compile Include="HtmlHelperRenderExtensions.cs" />
|
||||
<Compile Include="LegacyScheduledTasks.cs" />
|
||||
<Compile Include="Scheduling\Scheduler.cs" />
|
||||
<Compile Include="Media\EmbedProviders\AbstractOEmbedProvider.cs" />
|
||||
<Compile Include="Media\EmbedProviders\AbstractProvider.cs" />
|
||||
<Compile Include="Media\EmbedProviders\Flickr.cs" />
|
||||
@@ -1782,6 +1786,7 @@
|
||||
<Compile Include="WebServices\EmbedMediaService.cs" />
|
||||
<Compile Include="WebServices\ExamineManagementApiController.cs" />
|
||||
<Compile Include="WebServices\FolderBrowserService.cs" />
|
||||
<Compile Include="WebServices\ScheduledPublishController.cs" />
|
||||
<Compile Include="WebServices\UmbracoAuthorizedHttpHandler.cs" />
|
||||
<Compile Include="WebServices\SaveFileController.cs" />
|
||||
<Compile Include="WebServices\UmbracoAuthorizedWebService.cs">
|
||||
|
||||
@@ -24,6 +24,7 @@ using Umbraco.Web.PropertyEditors;
|
||||
using Umbraco.Web.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Scheduling;
|
||||
using Umbraco.Web.WebApi;
|
||||
using umbraco.BusinessLogic;
|
||||
using umbraco.presentation.cache;
|
||||
@@ -116,7 +117,7 @@ namespace Umbraco.Web
|
||||
protected override void InitializeApplicationEventsResolver()
|
||||
{
|
||||
base.InitializeApplicationEventsResolver();
|
||||
ApplicationEventsResolver.Current.AddType<LegacyScheduledTasks>();
|
||||
ApplicationEventsResolver.Current.AddType<Scheduler>();
|
||||
//We need to remove these types because we've obsoleted them and we don't want them executing:
|
||||
ApplicationEventsResolver.Current.RemoveType<global::umbraco.LibraryCacheRefresher>();
|
||||
}
|
||||
|
||||
59
src/Umbraco.Web/WebServices/ScheduledPublishController.cs
Normal file
59
src/Umbraco.Web/WebServices/ScheduledPublishController.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Web.Mvc;
|
||||
using umbraco;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Publishing;
|
||||
using Umbraco.Web.Mvc;
|
||||
|
||||
namespace Umbraco.Web.WebServices
|
||||
{
|
||||
//TODO: How to authenticate?
|
||||
|
||||
/// <summary>
|
||||
/// A REST controller used for running the scheduled publishing, this is called from the background worker timer
|
||||
/// </summary>
|
||||
public class ScheduledPublishController : UmbracoController
|
||||
{
|
||||
private static bool _isPublishingRunning = false;
|
||||
|
||||
[HttpPost]
|
||||
public JsonResult Index()
|
||||
{
|
||||
if (_isPublishingRunning)
|
||||
return null;
|
||||
_isPublishingRunning = true;
|
||||
|
||||
try
|
||||
{
|
||||
// DO not run publishing if content is re-loading
|
||||
if (content.Instance.isInitializing == false)
|
||||
{
|
||||
var publisher = new ScheduledPublisher(Services.ContentService);
|
||||
publisher.CheckPendingAndProcess();
|
||||
}
|
||||
|
||||
return Json(new
|
||||
{
|
||||
success = true
|
||||
});
|
||||
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
LogHelper.Error<ScheduledPublishController>("Error executing scheduled task", ee);
|
||||
|
||||
Response.StatusCode = 400;
|
||||
|
||||
return Json(new
|
||||
{
|
||||
success = false,
|
||||
message = ee.Message
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isPublishingRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,7 @@ using Umbraco.Core.Logging;
|
||||
|
||||
namespace umbraco.presentation
|
||||
{
|
||||
/// <summary>
|
||||
/// Makes a call to /umbraco/ping.aspx which is used to keep the web app alive
|
||||
/// </summary>
|
||||
[Obsolete("This is no longer used and will be removed in future versions")]
|
||||
public class keepAliveService
|
||||
{
|
||||
//NOTE: sender will be the umbraco ApplicationContext
|
||||
@@ -20,6 +18,8 @@ namespace umbraco.presentation
|
||||
|
||||
var appContext = (ApplicationContext) sender;
|
||||
|
||||
//TODO: This won't always work, in load balanced scenarios ping will not work because
|
||||
// this original request url will be public and not internal to the server.
|
||||
var url = string.Format("http://{0}/ping.aspx", appContext.OriginalRequestUrl);
|
||||
try
|
||||
{
|
||||
|
||||
@@ -4,15 +4,15 @@ using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Web;
|
||||
using System.Xml;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using umbraco.BusinessLogic;
|
||||
using umbraco.cms.businesslogic.web;
|
||||
using Umbraco.Core.Publishing;
|
||||
|
||||
namespace umbraco.presentation
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for publishingService.
|
||||
/// </summary>
|
||||
[Obsolete("This is no longer used and will be removed in future versions")]
|
||||
public class publishingService
|
||||
{
|
||||
private static readonly Hashtable ScheduledTaskTimes = new Hashtable();
|
||||
@@ -26,37 +26,10 @@ namespace umbraco.presentation
|
||||
_isPublishingRunning = true;
|
||||
try
|
||||
{
|
||||
// DO not run publishing if content is re-loading
|
||||
if(!content.Instance.isInitializing)
|
||||
{
|
||||
|
||||
foreach (var d in Document.GetDocumentsForRelease())
|
||||
{
|
||||
try
|
||||
{
|
||||
d.ReleaseDate = DateTime.MinValue; //new DateTime(1, 1, 1); // Causes release date to be null
|
||||
d.SaveAndPublish(d.User);
|
||||
}
|
||||
catch(Exception ee)
|
||||
{
|
||||
LogHelper.Error<publishingService>(string.Format("Error publishing node {0}", d.Id), ee);
|
||||
}
|
||||
}
|
||||
foreach(Document d in Document.GetDocumentsForExpiration())
|
||||
{
|
||||
try
|
||||
{
|
||||
d.ExpireDate = DateTime.MinValue;
|
||||
//run the scheduled publishing - we need to determine if this server
|
||||
|
||||
d.UnPublish();
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
LogHelper.Error<publishingService>(string.Format("Error unpublishing node {0}", d.Id), ee);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
var publisher = new ScheduledPublisher(ApplicationContext.Current.Services.ContentService);
|
||||
publisher.CheckPendingAndProcess();
|
||||
|
||||
// run scheduled url tasks
|
||||
try
|
||||
@@ -70,7 +43,7 @@ namespace umbraco.presentation
|
||||
foreach (XmlNode task in tasks)
|
||||
{
|
||||
bool runTask = false;
|
||||
if (!ScheduledTaskTimes.ContainsKey(task.Attributes.GetNamedItem("alias").Value))
|
||||
if (ScheduledTaskTimes.ContainsKey(task.Attributes.GetNamedItem("alias").Value) == false)
|
||||
{
|
||||
runTask = true;
|
||||
ScheduledTaskTimes.Add(task.Attributes.GetNamedItem("alias").Value, DateTime.Now);
|
||||
@@ -88,7 +61,7 @@ namespace umbraco.presentation
|
||||
|
||||
if (runTask)
|
||||
{
|
||||
bool taskResult = getTaskByHttp(task.Attributes.GetNamedItem("url").Value);
|
||||
bool taskResult = GetTaskByHttp(task.Attributes.GetNamedItem("url").Value);
|
||||
if (bool.Parse(task.Attributes.GetNamedItem("log").Value))
|
||||
LogHelper.Info<publishingService>(string.Format("{0} has been called with response: {1}", task.Attributes.GetNamedItem("alias").Value, taskResult));
|
||||
}
|
||||
@@ -111,26 +84,20 @@ namespace umbraco.presentation
|
||||
}
|
||||
}
|
||||
|
||||
private static bool getTaskByHttp(string url)
|
||||
private static bool GetTaskByHttp(string url)
|
||||
{
|
||||
var myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
|
||||
HttpWebResponse myHttpWebResponse = null;
|
||||
try
|
||||
{
|
||||
myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();
|
||||
if(myHttpWebResponse.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
myHttpWebResponse.Close();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
myHttpWebResponse.Close();
|
||||
return false;
|
||||
}
|
||||
using (myHttpWebResponse = (HttpWebResponse) myHttpWebRequest.GetResponse())
|
||||
{
|
||||
return myHttpWebResponse.StatusCode == HttpStatusCode.OK;
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<publishingService>("An error occurred calling web task for url: " + url, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user