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 {