diff --git a/src/Umbraco.Core/Publishing/ScheduledPublisher.cs b/src/Umbraco.Core/Publishing/ScheduledPublisher.cs
new file mode 100644
index 0000000000..45492423ab
--- /dev/null
+++ b/src/Umbraco.Core/Publishing/ScheduledPublisher.cs
@@ -0,0 +1,61 @@
+using System;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Models;
+using Umbraco.Core.Services;
+
+namespace Umbraco.Core.Publishing
+{
+ ///
+ /// Used to perform scheduled publishing/unpublishing
+ ///
+ 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("Could not published the document (" + d.Id + ") based on it's scheduled release, status result: " + result.Result.StatusType, result.Exception);
+ }
+ else
+ {
+ LogHelper.Warn("Could not published the document (" + d.Id + ") based on it's scheduled release. Status result: " + result.Result.StatusType);
+ }
+ }
+ }
+ catch (Exception ee)
+ {
+ LogHelper.Error(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(string.Format("Error unpublishing node {0}", d.Id), ee);
+ throw;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Sync/CurrentServerEnvironmentStatus.cs b/src/Umbraco.Core/Sync/CurrentServerEnvironmentStatus.cs
new file mode 100644
index 0000000000..95305b7cdc
--- /dev/null
+++ b/src/Umbraco.Core/Sync/CurrentServerEnvironmentStatus.cs
@@ -0,0 +1,28 @@
+namespace Umbraco.Core.Sync
+{
+ ///
+ /// The current status of the server in the Umbraco environment
+ ///
+ internal enum CurrentServerEnvironmentStatus
+ {
+ ///
+ /// If the current server is detected as the 'master' server when configured in a load balanced scenario
+ ///
+ Master,
+
+ ///
+ /// If the current server is detected as a 'slave' server when configured in a load balanced scenario
+ ///
+ Slave,
+
+ ///
+ /// If the current server cannot be detected as a 'slave' or 'master' when configured in a load balanced scenario
+ ///
+ Unknown,
+
+ ///
+ /// If load balancing is not enabled and this is the only server in the umbraco environment
+ ///
+ Single
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs b/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs
new file mode 100644
index 0000000000..c5363b494f
--- /dev/null
+++ b/src/Umbraco.Core/Sync/ServerEnvironmentHelper.cs
@@ -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
+{
+ ///
+ /// A helper used to determine the current server environment status
+ ///
+ internal static class ServerEnvironmentHelper
+ {
+ ///
+ /// 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.
+ ///
+ /// The full base url including schema (i.e. http://myserver:80/umbraco )
+ 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().ToList();
+
+ foreach (var xmlNode in xmlNodes)
+ {
+ var appId = xmlNode.AttributeValue("appId");
+ var serverName = xmlNode.AttributeValue("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("forceProtocol").IsNullOrWhiteSpace() ? "http" : xmlNode.AttributeValue("forceProtocol"),
+ xmlNode.InnerText,
+ xmlNode.AttributeValue("forcePortnumber").IsNullOrWhiteSpace() ? "80" : xmlNode.AttributeValue("forcePortnumber"),
+ IOHelper.ResolveUrl(SystemDirectories.Umbraco).TrimStart('/'));
+ }
+ }
+
+ //cannot be determined, then the base url has to be the first url registered
+ return ApplicationContext.Current.OriginalRequestUrl;
+ }
+
+ ///
+ /// Returns the current environment status for the current server
+ ///
+ ///
+ 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().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("appId");
+ var serverName = master.AttributeValue("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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 89752d47ed..d0d0cd573b 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -770,6 +770,7 @@
+
@@ -830,7 +831,9 @@
+
+
diff --git a/src/Umbraco.Tests/TypeHelperTests.cs b/src/Umbraco.Tests/TypeHelperTests.cs
index f637b1ff20..6e9ee10152 100644
--- a/src/Umbraco.Tests/TypeHelperTests.cs
+++ b/src/Umbraco.Tests/TypeHelperTests.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);
diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config
index 0dc21ae2fc..0851581225 100644
--- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config
+++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config
@@ -223,16 +223,47 @@
-
+
0
+
+
+
-
-
-
-
+
+
+
diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config
index a089b97608..0a60537af5 100644
--- a/src/Umbraco.Web.UI/config/umbracoSettings.config
+++ b/src/Umbraco.Web.UI/config/umbracoSettings.config
@@ -223,16 +223,51 @@
-
-
+
+
0
+
+
+
-
-
-
-
+
+
+
+ localhost
+ umb1.dev
+ umb2.dev
+
diff --git a/src/Umbraco.Web/LegacyScheduledTasks.cs b/src/Umbraco.Web/LegacyScheduledTasks.cs
deleted file mode 100644
index 8b4ccce8e9..0000000000
--- a/src/Umbraco.Web/LegacyScheduledTasks.cs
+++ /dev/null
@@ -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("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("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
- }
-}
diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs
new file mode 100644
index 0000000000..443c3727e1
--- /dev/null
+++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs
@@ -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("Error in ping", ee);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs
new file mode 100644
index 0000000000..cea4d1b01c
--- /dev/null
+++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs
@@ -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("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("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());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs
new file mode 100644
index 0000000000..7a249ce4ac
--- /dev/null
+++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs
@@ -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("An error occurred with the scheduled publishing", ee);
+ }
+ finally
+ {
+ _isPublishingRunning = false;
+ }
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs
new file mode 100644
index 0000000000..9abf7d9f69
--- /dev/null
+++ b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs
@@ -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("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(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("An error occurred calling web task for url: " + url, ex);
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Scheduling/Scheduler.cs b/src/Umbraco.Web/Scheduling/Scheduler.cs
new file mode 100644
index 0000000000..75930fed4c
--- /dev/null
+++ b/src/Umbraco.Web/Scheduling/Scheduler.cs
@@ -0,0 +1,82 @@
+using System.Threading;
+using Umbraco.Core;
+using Umbraco.Core.Sync;
+
+namespace Umbraco.Web.Scheduling
+{
+ ///
+ /// Used to do the scheduling for tasks, publishing, etc...
+ ///
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ 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();
+ }
+
+ ///
+ /// This performs all of the scheduling on the one timer
+ ///
+ ///
+ ///
+ /// No processing will be done if this server is a slave
+ ///
+ 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;
+ }
+ }
+
+ }
+}
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index 49da691cd2..a8bcafd5f7 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -325,6 +325,10 @@
+
+
+
+
@@ -499,7 +503,7 @@
ASPXCodeBehind
-
+
@@ -1782,6 +1786,7 @@
+
diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs
index 05398a708c..5a313b2350 100644
--- a/src/Umbraco.Web/WebBootManager.cs
+++ b/src/Umbraco.Web/WebBootManager.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();
+ ApplicationEventsResolver.Current.AddType();
//We need to remove these types because we've obsoleted them and we don't want them executing:
ApplicationEventsResolver.Current.RemoveType();
}
diff --git a/src/Umbraco.Web/WebServices/ScheduledPublishController.cs b/src/Umbraco.Web/WebServices/ScheduledPublishController.cs
new file mode 100644
index 0000000000..bc2b5df18c
--- /dev/null
+++ b/src/Umbraco.Web/WebServices/ScheduledPublishController.cs
@@ -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?
+
+ ///
+ /// A REST controller used for running the scheduled publishing, this is called from the background worker timer
+ ///
+ 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("Error executing scheduled task", ee);
+
+ Response.StatusCode = 400;
+
+ return Json(new
+ {
+ success = false,
+ message = ee.Message
+ });
+ }
+ finally
+ {
+ _isPublishingRunning = false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs b/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs
index 4ab1646a9e..6d80d8bedd 100644
--- a/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs
+++ b/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs
@@ -7,9 +7,7 @@ using Umbraco.Core.Logging;
namespace umbraco.presentation
{
- ///
- /// Makes a call to /umbraco/ping.aspx which is used to keep the web app alive
- ///
+ [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
{
diff --git a/src/Umbraco.Web/umbraco.presentation/publishingService.cs b/src/Umbraco.Web/umbraco.presentation/publishingService.cs
index d22165d648..7bd85236e6 100644
--- a/src/Umbraco.Web/umbraco.presentation/publishingService.cs
+++ b/src/Umbraco.Web/umbraco.presentation/publishingService.cs
@@ -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 description for publishingService.
- ///
+ [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(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(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(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("An error occurred calling web task for url: " + url, ex);
}
finally
{