diff --git a/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj b/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj
index 2456da972..ab32431f2 100644
--- a/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj
+++ b/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj
@@ -169,6 +169,8 @@
+
+
@@ -254,6 +256,10 @@
{79AED36E-ABD0-4747-93D3-8722B042454B}Orchard.Users
+
+ {9CD5C81F-5828-4384-8474-2E2BE71D5EDD}
+ Orchard.Warmup
+ {194D3CCC-1153-474D-8176-FDE8D7D0D0BD}Orchard.Widgets
diff --git a/src/Orchard.Tests.Modules/Warmup/WarmupUpdaterTests.cs b/src/Orchard.Tests.Modules/Warmup/WarmupUpdaterTests.cs
new file mode 100644
index 000000000..71a704791
--- /dev/null
+++ b/src/Orchard.Tests.Modules/Warmup/WarmupUpdaterTests.cs
@@ -0,0 +1,281 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Xml;
+using Autofac;
+using Moq;
+using NUnit.Framework;
+using Orchard.Environment.Warmup;
+using Orchard.FileSystems.AppData;
+using Orchard.FileSystems.LockFile;
+using Orchard.Services;
+using Orchard.Tests.FileSystems.AppData;
+using Orchard.Tests.Stubs;
+using Orchard.Tests.UI.Navigation;
+using Orchard.Warmup.Models;
+using Orchard.Warmup.Services;
+
+namespace Orchard.Tests.Modules.Warmup {
+ public class WarmupUpdaterTests {
+ protected IContainer _container;
+ private IWarmupUpdater _warmupUpdater;
+ private IAppDataFolder _appDataFolder;
+ private ILockFileManager _lockFileManager;
+ private StubClock _clock;
+ private Mock _webDownloader;
+ private IOrchardServices _orchardServices;
+ private WarmupSettingsPart _settings;
+
+ private readonly string _basePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
+
+ private string _warmupFilename, _lockFilename;
+ private const string WarmupFolder = "Warmup";
+
+ [TestFixtureTearDown]
+ public void Clean() {
+ if (Directory.Exists(_basePath)) {
+ Directory.Delete(_basePath, true);
+ }
+ }
+
+ [SetUp]
+ public void Init() {
+ if (Directory.Exists(_basePath)) {
+ Directory.Delete(_basePath, true);
+ }
+
+ Directory.CreateDirectory(_basePath);
+ _appDataFolder = AppDataFolderTests.CreateAppDataFolder(_basePath);
+ _webDownloader = new Mock();
+ _orchardServices = new StubOrchardServices();
+ ((StubWorkContextAccessor.WorkContextImpl.StubSite) _orchardServices.WorkContext.CurrentSite).BaseUrl = "http://orchardproject.net";
+
+ _settings = new WarmupSettingsPart { Record = new WarmupSettingsPartRecord() };
+ _orchardServices.WorkContext.CurrentSite.ContentItem.Weld(_settings);
+
+ var builder = new ContainerBuilder();
+ builder.RegisterInstance(_appDataFolder).As();
+ builder.RegisterInstance(_orchardServices).As();
+ builder.RegisterType().As();
+ builder.RegisterType().As();
+ builder.RegisterType().As();
+ builder.RegisterInstance(_clock = new StubClock()).As();
+ builder.RegisterInstance(_webDownloader.Object).As();
+ _container = builder.Build();
+
+ _lockFileManager = _container.Resolve();
+ _warmupUpdater = _container.Resolve();
+
+ _warmupFilename = _appDataFolder.Combine(WarmupFolder, "warmup.txt");
+ _lockFilename = _appDataFolder.Combine(WarmupFolder, "warmup.txt.lock");
+ }
+
+ [Test]
+ public void ShouldDoNothingWhenNoUrlsAreSpecified() {
+ _warmupUpdater.EnsureGenerate();
+ Assert.That(_appDataFolder.ListFiles(WarmupFolder).Count(), Is.EqualTo(0));
+ }
+
+ [Test]
+ public void StampFileShouldBeDeletedToForceAnUpdate() {
+ _appDataFolder.CreateFile(_warmupFilename, "");
+ _warmupUpdater.Generate();
+ Assert.That(_appDataFolder.ListFiles(WarmupFolder).Count(), Is.EqualTo(0));
+ }
+
+ [Test]
+ public void GenerateShouldNotRunIfLocked() {
+ _appDataFolder.CreateFile(_warmupFilename, "");
+ ILockFile lockFile = null;
+ _lockFileManager.TryAcquireLock(_lockFilename, ref lockFile);
+ using(lockFile) {
+ _warmupUpdater.Generate();
+ // warmup file + lock file
+ Assert.That(_appDataFolder.ListFiles(WarmupFolder).Count(), Is.EqualTo(2));
+ }
+
+ _warmupUpdater.Generate();
+ Assert.That(_appDataFolder.ListFiles(WarmupFolder).Count(), Is.EqualTo(0));
+ }
+
+ [Test]
+ public void ShouldDownloadConfiguredUrls() {
+ _settings.Urls = @" /
+ /About";
+ _webDownloader
+ .Setup(w => w.Download("http://orchardproject.net/"))
+ .Returns(new DownloadResult { Content = "Foo", StatusCode = HttpStatusCode.OK });
+
+ _webDownloader
+ .Setup(w => w.Download("http://orchardproject.net/About"))
+ .Returns(new DownloadResult { Content = "Bar", StatusCode = HttpStatusCode.OK });
+
+ _warmupUpdater.Generate();
+ var files = _appDataFolder.ListFiles(WarmupFolder).ToList();
+
+ // warmup + content files
+ Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, "warmup.txt")));
+ Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/"))));
+ Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About"))));
+
+ var homepageContent = _appDataFolder.ReadFile(_appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/")));
+ var aboutcontent = _appDataFolder.ReadFile(_appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About")));
+
+ Assert.That(homepageContent, Is.EqualTo("Foo"));
+ Assert.That(aboutcontent, Is.EqualTo("Bar"));
+ }
+
+ [Test]
+ public void ShouldCreateFilesForOkStatusOnly() {
+ _settings.Urls = @" /
+ /About";
+ _webDownloader
+ .Setup(w => w.Download("http://orchardproject.net/"))
+ .Returns(new DownloadResult { Content = "Foo", StatusCode = HttpStatusCode.OK });
+
+ _webDownloader
+ .Setup(w => w.Download("http://orchardproject.net/About"))
+ .Returns(new DownloadResult { Content = "Bar", StatusCode = HttpStatusCode.NotFound });
+
+ _warmupUpdater.Generate();
+ var files = _appDataFolder.ListFiles(WarmupFolder).ToList();
+
+ // warmup + content file
+ Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, "warmup.txt")));
+ Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/"))));
+ Assert.That(files, Has.None.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About"))));
+ }
+
+ [Test]
+ public void ShouldProcessValidRequestsOnly() {
+ _settings.Urls = @" /
+ <>@\\";
+ _webDownloader
+ .Setup(w => w.Download("http://orchardproject.net/"))
+ .Returns(new DownloadResult { Content = "Foo", StatusCode = HttpStatusCode.OK });
+
+ _warmupUpdater.Generate();
+ var files = _appDataFolder.ListFiles(WarmupFolder).ToList();
+
+ // warmup + content file
+ Assert.That(files.Count, Is.EqualTo(2));
+ Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, "warmup.txt")));
+ Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/"))));
+ }
+
+ [Test]
+ public void WarmupFileShouldContainUtcNow() {
+ _settings.Urls = @"/";
+
+ _webDownloader
+ .Setup(w => w.Download("http://orchardproject.net/"))
+ .Returns(new DownloadResult { Content = "Foo", StatusCode = HttpStatusCode.OK });
+
+ _warmupUpdater.Generate();
+
+ var warmupContent = _appDataFolder.ReadFile(_warmupFilename);
+ Assert.That(warmupContent, Is.EqualTo(XmlConvert.ToString(_clock.UtcNow, XmlDateTimeSerializationMode.Utc)));
+ }
+
+ [Test]
+ public void ShouldNotProcessIfDelayHasNotExpired() {
+ _settings.Urls = @"/";
+ _settings.Delay = 90;
+ _settings.Scheduled = true;
+
+ _webDownloader
+ .Setup(w => w.Download("http://orchardproject.net/"))
+ .Returns(new DownloadResult { Content = "Foo", StatusCode = HttpStatusCode.OK });
+
+ _webDownloader
+ .Setup(w => w.Download("http://orchardproject.net/About"))
+ .Returns(new DownloadResult { Content = "Bar", StatusCode = HttpStatusCode.OK });
+
+ _warmupUpdater.Generate();
+
+ var warmupContent = _appDataFolder.ReadFile(_warmupFilename);
+ Assert.That(warmupContent, Is.EqualTo(XmlConvert.ToString(_clock.UtcNow, XmlDateTimeSerializationMode.Utc)));
+
+ _settings.Urls = @" /
+ /About";
+ _clock.Advance(TimeSpan.FromMinutes(89));
+ _warmupUpdater.EnsureGenerate();
+
+ var files = _appDataFolder.ListFiles(WarmupFolder).ToList();
+ Assert.That(files, Has.None.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About"))));
+
+ warmupContent = _appDataFolder.ReadFile(_warmupFilename);
+ Assert.That(warmupContent, Is.Not.EqualTo(XmlConvert.ToString(_clock.UtcNow, XmlDateTimeSerializationMode.Utc)));
+ }
+
+ [Test]
+ public void ShouldProcessIfDelayHasExpired() {
+ _settings.Urls = @"/";
+ _settings.Delay = 90;
+ _settings.Scheduled = true;
+
+ _webDownloader
+ .Setup(w => w.Download("http://orchardproject.net/"))
+ .Returns(new DownloadResult { Content = "Foo", StatusCode = HttpStatusCode.OK });
+
+ _webDownloader
+ .Setup(w => w.Download("http://orchardproject.net/About"))
+ .Returns(new DownloadResult { Content = "Bar", StatusCode = HttpStatusCode.OK });
+
+ _warmupUpdater.Generate();
+
+ var warmupContent = _appDataFolder.ReadFile(_warmupFilename);
+ Assert.That(warmupContent, Is.EqualTo(XmlConvert.ToString(_clock.UtcNow, XmlDateTimeSerializationMode.Utc)));
+
+ _settings.Urls = @" /
+ /About";
+ _clock.Advance(TimeSpan.FromMinutes(91));
+ _warmupUpdater.EnsureGenerate();
+
+ var files = _appDataFolder.ListFiles(WarmupFolder).ToList();
+ Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About"))));
+
+ warmupContent = _appDataFolder.ReadFile(_warmupFilename);
+ Assert.That(warmupContent, Is.EqualTo(XmlConvert.ToString(_clock.UtcNow, XmlDateTimeSerializationMode.Utc)));
+ }
+
+ [Test]
+ public void ShouldGenerateNonWwwVersions() {
+ _settings.Urls = @" /
+ /About";
+
+ ((StubWorkContextAccessor.WorkContextImpl.StubSite)_orchardServices.WorkContext.CurrentSite).BaseUrl = "http://www.orchardproject.net";
+
+ _webDownloader
+ .Setup(w => w.Download("http://www.orchardproject.net/"))
+ .Returns(new DownloadResult { Content = "Foo", StatusCode = HttpStatusCode.OK });
+
+ _webDownloader
+ .Setup(w => w.Download("http://www.orchardproject.net/About"))
+ .Returns(new DownloadResult { Content = "Bar", StatusCode = HttpStatusCode.OK });
+
+ _warmupUpdater.Generate();
+ var files = _appDataFolder.ListFiles(WarmupFolder).ToList();
+
+ // warmup + content files
+ Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, "warmup.txt")));
+ Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://www.orchardproject.net/"))));
+ Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://www.orchardproject.net/About"))));
+ Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/"))));
+ Assert.That(files, Has.Some.Matches(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About"))));
+
+ var homepageContent = _appDataFolder.ReadFile(_appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/")));
+ var aboutcontent = _appDataFolder.ReadFile(_appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About")));
+
+ var wwwhomepageContent = _appDataFolder.ReadFile(_appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://www.orchardproject.net/")));
+ var wwwaboutcontent = _appDataFolder.ReadFile(_appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://www.orchardproject.net/About")));
+
+ Assert.That(homepageContent, Is.EqualTo("Foo"));
+ Assert.That(wwwhomepageContent, Is.EqualTo("Foo"));
+ Assert.That(aboutcontent, Is.EqualTo("Bar"));
+ Assert.That(wwwaboutcontent, Is.EqualTo("Bar"));
+ }
+
+ }
+}
diff --git a/src/Orchard.Tests.Modules/Warmup/WebDownloaderTests.cs b/src/Orchard.Tests.Modules/Warmup/WebDownloaderTests.cs
new file mode 100644
index 000000000..66fbd993b
--- /dev/null
+++ b/src/Orchard.Tests.Modules/Warmup/WebDownloaderTests.cs
@@ -0,0 +1,37 @@
+using System.Net;
+using NUnit.Framework;
+using Orchard.Warmup.Services;
+
+namespace Orchard.Tests.Modules.Warmup {
+ public class WebDownloaderTests {
+ private readonly IWebDownloader _webDownloader = new WebDownloader();
+
+ [Test]
+ public void ShouldReturnNullWhenUrlIsEmpty() {
+ Assert.That(_webDownloader.Download(null), Is.Null);
+ Assert.That(_webDownloader.Download(""), Is.Null);
+ Assert.That(_webDownloader.Download(" "), Is.Null);
+ }
+
+ [Test]
+ public void ShouldReturnNullWhenUrlIsInvalid() {
+ Assert.That(_webDownloader.Download("froutfrout|yepyep"), Is.Null);
+ }
+
+ [Test]
+ public void StatusCodeShouldBe404ForUnexistingResources() {
+ var download = _webDownloader.Download("http://www.microsoft.com/yepyep");
+ Assert.That(download, Is.Not.Null);
+ Assert.That(download.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
+ Assert.That(download.Content, Is.Null);
+ }
+
+ [Test]
+ public void StatusCodeShouldBe200ForValidRequests() {
+ var download = _webDownloader.Download("http://www.microsoft.com/");
+ Assert.That(download, Is.Not.Null);
+ Assert.That(download.StatusCode, Is.EqualTo(HttpStatusCode.OK));
+ Assert.That(download.Content, Is.Not.Empty);
+ }
+ }
+}
diff --git a/src/Orchard.Tests/Environment/WarmUp/WarmUpUtilityTests.cs b/src/Orchard.Tests/Environment/WarmUp/WarmUpUtilityTests.cs
new file mode 100644
index 000000000..d402adc42
--- /dev/null
+++ b/src/Orchard.Tests/Environment/WarmUp/WarmUpUtilityTests.cs
@@ -0,0 +1,28 @@
+using System;
+using NUnit.Framework;
+using Orchard.Environment.Warmup;
+
+namespace Orchard.Tests.Environment.Warmup {
+ [TestFixture]
+ public class WarmUpUtilityTests {
+
+ [Test]
+ public void EmptyStringsAreNotAllowed() {
+ Assert.Throws(() => WarmupUtility.EncodeUrl(""));
+ Assert.Throws(() => WarmupUtility.EncodeUrl(null));
+ }
+
+ [Test]
+ public void EncodedUrlsShouldBeValidFilenames() {
+ Assert.That(WarmupUtility.EncodeUrl("http://www.microsoft.com"), Is.EqualTo("http_3A_2F_2Fwww_2Emicrosoft_2Ecom"));
+ Assert.That(WarmupUtility.EncodeUrl("http://www.microsoft.com/foo?bar=baz"), Is.EqualTo("http_3A_2F_2Fwww_2Emicrosoft_2Ecom_2Ffoo_3Fbar_3Dbaz"));
+ }
+
+ [Test]
+ public void EncodedUrlsShouldPreserveQueryStrings() {
+ Assert.That(WarmupUtility.EncodeUrl("http://www.microsoft.com/foo?bar=baz"), Is.StringContaining("bar"));
+ Assert.That(WarmupUtility.EncodeUrl("http://www.microsoft.com/foo?bar=baz"), Is.StringContaining("baz"));
+ Assert.That(WarmupUtility.EncodeUrl("http://www.microsoft.com/foo?bar=baz"), Is.StringContaining("foo"));
+ }
+ }
+}
diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj
index 9d1c806f2..57f4a1b23 100644
--- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj
+++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj
@@ -239,6 +239,7 @@
+
diff --git a/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs b/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs
index cae6eb4f4..807ffc968 100644
--- a/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs
+++ b/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs
@@ -71,10 +71,12 @@ namespace Orchard.Tests.Stubs {
set { throw new NotImplementedException(); }
}
- public int PageSize {
+ public int PageSize{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
+
+ public string BaseUrl { get; set;}
}
public class StubUser : IUser {
diff --git a/src/Orchard.Tests/UI/Navigation/NavigationManagerTests.cs b/src/Orchard.Tests/UI/Navigation/NavigationManagerTests.cs
index 1aa26edec..304bd1203 100644
--- a/src/Orchard.Tests/UI/Navigation/NavigationManagerTests.cs
+++ b/src/Orchard.Tests/UI/Navigation/NavigationManagerTests.cs
@@ -140,8 +140,15 @@ namespace Orchard.Tests.UI.Navigation {
get { throw new NotImplementedException(); }
}
+ private WorkContext _workContext;
public WorkContext WorkContext {
- get { return new StubWorkContextAccessor(_lifetimeScope).GetContext(); }
+ get {
+ if(_workContext == null) {
+ _workContext = new StubWorkContextAccessor(_lifetimeScope).GetContext();
+ }
+
+ return _workContext;
+ }
}
}
}
diff --git a/src/Orchard.Web/Core/Settings/Controllers/AdminController.cs b/src/Orchard.Web/Core/Settings/Controllers/AdminController.cs
index 99d768df8..af64ffb51 100644
--- a/src/Orchard.Web/Core/Settings/Controllers/AdminController.cs
+++ b/src/Orchard.Web/Core/Settings/Controllers/AdminController.cs
@@ -64,27 +64,26 @@ namespace Orchard.Core.Settings.Controllers {
var site = _siteService.GetSiteSettings();
dynamic model = Services.ContentManager.UpdateEditor(site, this, groupInfoId);
+ GroupInfo groupInfo = null;
+
if (!string.IsNullOrWhiteSpace(groupInfoId)) {
if (model == null) {
Services.TransactionManager.Cancel();
return HttpNotFound();
}
- var groupInfo = Services.ContentManager.GetEditorGroupInfo(site, groupInfoId);
+ groupInfo = Services.ContentManager.GetEditorGroupInfo(site, groupInfoId);
if (groupInfo == null) {
Services.TransactionManager.Cancel();
return HttpNotFound();
}
-
- if (!ModelState.IsValid) {
- Services.TransactionManager.Cancel();
- model.GroupInfo = groupInfo;
-
- // Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
- return View((object) model);
- }
}
- else {
+
+ if (!ModelState.IsValid) {
+ Services.TransactionManager.Cancel();
+ model.GroupInfo = groupInfo;
+
+ // Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
return View((object)model);
}
diff --git a/src/Orchard.Web/Core/Settings/Drivers/SiteSettingsPartDriver.cs b/src/Orchard.Web/Core/Settings/Drivers/SiteSettingsPartDriver.cs
index 29b0d6204..54fc9437d 100644
--- a/src/Orchard.Web/Core/Settings/Drivers/SiteSettingsPartDriver.cs
+++ b/src/Orchard.Web/Core/Settings/Drivers/SiteSettingsPartDriver.cs
@@ -1,9 +1,11 @@
-using JetBrains.Annotations;
+using System.Net;
+using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.Core.Settings.Models;
using Orchard.Core.Settings.ViewModels;
using Orchard.Localization.Services;
+using Orchard.Logging;
using Orchard.Settings;
using System;
using Orchard.Security;
@@ -16,16 +18,24 @@ namespace Orchard.Core.Settings.Drivers {
private readonly ISiteService _siteService;
private readonly ICultureManager _cultureManager;
private readonly IMembershipService _membershipService;
+ private readonly INotifier _notifier;
- public SiteSettingsPartDriver(ISiteService siteService, ICultureManager cultureManager, IMembershipService membershipService, INotifier notifier) {
+ public SiteSettingsPartDriver(
+ ISiteService siteService,
+ ICultureManager cultureManager,
+ IMembershipService membershipService,
+ INotifier notifier) {
_siteService = siteService;
_cultureManager = cultureManager;
_membershipService = membershipService;
+ _notifier = notifier;
T = NullLocalizer.Instance;
+ Logger = NullLogger.Instance;
}
public Localizer T { get; set; }
+ public ILogger Logger { get; set; }
protected override string Prefix { get { return "SiteSettings"; } }
@@ -48,6 +58,8 @@ namespace Orchard.Core.Settings.Drivers {
SiteCultures = _cultureManager.ListCultures()
};
+ var previousBaseUrl = model.Site.BaseUrl;
+
updater.TryUpdateModel(model, Prefix, null, null);
// ensures the super user is fully empty
@@ -62,6 +74,27 @@ namespace Orchard.Core.Settings.Drivers {
}
}
+
+ // ensure the base url is absolute if provided
+ if (!String.IsNullOrWhiteSpace(model.Site.BaseUrl)) {
+ if (!model.Site.BaseUrl.ToLower().StartsWith("http")) {
+ updater.AddModelError("BaseUrl", T("The base url must be absolute."));
+ }
+ // if the base url has been modified, try to ping it
+ else if (!String.Equals(previousBaseUrl, model.Site.BaseUrl, StringComparison.OrdinalIgnoreCase)) {
+ try {
+ var request = WebRequest.Create(model.Site.BaseUrl) as HttpWebRequest;
+ if (request != null) {
+ using (request.GetResponse() as HttpWebResponse) {}
+ }
+ }
+ catch (Exception e) {
+ _notifier.Warning(T("The base url you entered could not be requested from current location."));
+ Logger.Warning(e, "Could not query base url: {0}", model.Site.BaseUrl);
+ }
+ }
+ }
+
return ContentShape("Parts_Settings_SiteSettingsPart",
() => shapeHelper.EditorTemplate(TemplateName: "Parts.Settings.SiteSettingsPart", Model: model, Prefix: Prefix));
}
diff --git a/src/Orchard.Web/Core/Settings/Migrations.cs b/src/Orchard.Web/Core/Settings/Migrations.cs
index 57d45a144..8af6255f5 100644
--- a/src/Orchard.Web/Core/Settings/Migrations.cs
+++ b/src/Orchard.Web/Core/Settings/Migrations.cs
@@ -95,5 +95,14 @@ namespace Orchard.Core.Settings {
return 1;
}
+
+ public int UpdateFrom1() {
+ SchemaBuilder.AlterTable("SiteSettingsPartRecord",
+ table => table
+ .AddColumn("BaseUrl", c => c.WithLength(255))
+ );
+
+ return 2;
+ }
}
}
\ No newline at end of file
diff --git a/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs b/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs
index 3e5094699..46beb3444 100644
--- a/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs
+++ b/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs
@@ -1,4 +1,5 @@
-using Orchard.ContentManagement;
+using System.ComponentModel.DataAnnotations;
+using Orchard.ContentManagement;
using Orchard.Settings;
namespace Orchard.Core.Settings.Models {
@@ -42,5 +43,11 @@ namespace Orchard.Core.Settings.Models {
get { return Record.PageSize; }
set { Record.PageSize = value; }
}
+
+ [StringLength(255)]
+ public string BaseUrl {
+ get { return Record.BaseUrl; }
+ set { Record.BaseUrl = value; }
+ }
}
}
diff --git a/src/Orchard.Web/Core/Settings/Models/SiteSettingsPartRecord.cs b/src/Orchard.Web/Core/Settings/Models/SiteSettingsPartRecord.cs
index eb87877f5..7eb2764e0 100644
--- a/src/Orchard.Web/Core/Settings/Models/SiteSettingsPartRecord.cs
+++ b/src/Orchard.Web/Core/Settings/Models/SiteSettingsPartRecord.cs
@@ -1,4 +1,5 @@
-using Orchard.ContentManagement.Records;
+using System.ComponentModel.DataAnnotations;
+using Orchard.ContentManagement.Records;
using Orchard.Settings;
namespace Orchard.Core.Settings.Models {
@@ -24,5 +25,8 @@ namespace Orchard.Core.Settings.Models {
public virtual ResourceDebugMode ResourceDebugMode { get; set; }
public virtual int PageSize { get; set; }
+
+ [StringLength(255)]
+ public virtual string BaseUrl { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Orchard.Web/Core/Settings/ViewModels/SiteSettingsPartViewModel.cs b/src/Orchard.Web/Core/Settings/ViewModels/SiteSettingsPartViewModel.cs
index 8bbb87d40..e140e8841 100644
--- a/src/Orchard.Web/Core/Settings/ViewModels/SiteSettingsPartViewModel.cs
+++ b/src/Orchard.Web/Core/Settings/ViewModels/SiteSettingsPartViewModel.cs
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Web.Mvc;
-using Orchard.ContentManagement;
using Orchard.Core.Settings.Models;
using Orchard.Settings;
@@ -8,7 +7,6 @@ namespace Orchard.Core.Settings.ViewModels {
public class SiteSettingsPartViewModel {
public SiteSettingsPart Site { get; set; }
public IEnumerable SiteCultures { get; set; }
-
[HiddenInput(DisplayValue = false)]
public int Id {
@@ -16,33 +14,38 @@ namespace Orchard.Core.Settings.ViewModels {
}
public string PageTitleSeparator {
- get { return Site.Record.PageTitleSeparator; }
- set { Site.Record.PageTitleSeparator = value; }
+ get { return Site.PageTitleSeparator; }
+ set { Site.PageTitleSeparator = value; }
}
public string SiteName {
- get { return Site.Record.SiteName; }
- set { Site.Record.SiteName = value; }
+ get { return Site.SiteName; }
+ set { Site.SiteName = value; }
}
public string SiteCulture {
- get { return Site.Record.SiteCulture; }
- set { Site.Record.SiteCulture = value; }
+ get { return Site.SiteCulture; }
+ set { Site.SiteCulture = value; }
}
public string SuperUser {
- get { return Site.As().Record.SuperUser; }
- set { Site.As().Record.SuperUser = value; }
+ get { return Site.SuperUser; }
+ set { Site.SuperUser = value; }
}
public ResourceDebugMode ResourceDebugMode {
- get { return Site.As().ResourceDebugMode; }
- set { Site.As().ResourceDebugMode = value; }
+ get { return Site.ResourceDebugMode; }
+ set { Site.ResourceDebugMode = value; }
}
public int PageSize {
- get { return Site.As().PageSize; }
- set { Site.As().PageSize = value; }
+ get { return Site.PageSize; }
+ set { Site.PageSize = value; }
+ }
+
+ public string BaseUrl {
+ get { return Site.BaseUrl; }
+ set { Site.BaseUrl = value; }
}
}
}
diff --git a/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml b/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml
index e8d36de86..027ace416 100644
--- a/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml
+++ b/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml
@@ -38,7 +38,13 @@
- @Html.TextBoxFor(m => m.PageSize, new { @class = "textMedium" })
+ @Html.TextBoxFor(m => m.PageSize, new { @class = "text-small" })
@T("Determines the default number of items that are shown per page.")
+
+
+ @Html.TextBoxFor(m => m.BaseUrl, new { @class = "textMedium" })
+ @T("Enter the fully qualified base url of your website.")
+ @T("e.g., http://localhost:30320/orchardlocal, http://www.yourdomain.com")
+
\ No newline at end of file
diff --git a/src/Orchard.Web/Global.asax.cs b/src/Orchard.Web/Global.asax.cs
index 996bc4420..07f80f67e 100644
--- a/src/Orchard.Web/Global.asax.cs
+++ b/src/Orchard.Web/Global.asax.cs
@@ -1,35 +1,106 @@
-using System.Web;
+using System;
+using System.IO;
+using System.Threading;
+using System.Web;
+using System.Web.Hosting;
using System.Web.Mvc;
using System.Web.Routing;
using Autofac;
using Orchard.Environment;
+using Orchard.Environment.Warmup;
+using Orchard.Utility.Extensions;
namespace Orchard.Web {
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : HttpApplication {
- private static IOrchardHost _host;
+ private static StartupResult _startupResult;
+ private static EventWaitHandle _waitHandle;
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
}
protected void Application_Start() {
- RegisterRoutes(RouteTable.Routes);
+ LaunchStartupThread();
+ }
- _host = OrchardStarter.CreateHost(MvcSingletons);
- _host.Initialize();
+ ///
+ /// Initializes Orchard's Host in a separate thread
+ ///
+ private static void LaunchStartupThread() {
+ _startupResult = new StartupResult();
+ _waitHandle = new AutoResetEvent(false);
+
+ ThreadPool.QueueUserWorkItem(
+ state => {
+ try {
+ RegisterRoutes(RouteTable.Routes);
+ var host = OrchardStarter.CreateHost(MvcSingletons);
+ host.Initialize();
+ _startupResult.Host = host;
+ }
+ catch (Exception e) {
+ _startupResult.Error = e;
+ }
+ finally {
+ _waitHandle.Set();
+ }
+ });
}
protected void Application_BeginRequest() {
- Context.Items["originalHttpContext"] = Context;
+ // Host is still starting up?
+ if (_startupResult.Host == null && _startupResult.Error == null) {
- _host.BeginRequest();
+ // use the url as it was requested by the client
+ // the real url might be different if it has been translated (proxy, load balancing, ...)
+ var url = Request.ToUrlString();
+ var virtualFileCopy = "~/App_Data/WarmUp/" + WarmupUtility.EncodeUrl(url.Trim('/'));
+ var localCopy = HostingEnvironment.MapPath(virtualFileCopy);
+
+ if (File.Exists(localCopy)) {
+
+ // result should not be cached, even on proxies
+ Context.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
+ Context.Response.Cache.SetValidUntilExpires(false);
+ Context.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
+ Context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
+ Context.Response.Cache.SetNoStore();
+
+ Context.Response.ContentType = "text/html";
+
+ Context.Response.WriteFile(localCopy);
+ Context.Response.End();
+ }
+ else {
+ // there is no local copy and the host is not running
+ // wait for the host to initialize
+ _waitHandle.WaitOne();
+ }
+ }
+ else {
+ if (_startupResult.Error != null) {
+ // Host startup resulted in an error
+
+ // Throw error once, and restart launch (machine state may have changed
+ // so we need to simulate a "restart".
+ var error = _startupResult.Error;
+ LaunchStartupThread();
+ throw error;
+ }
+
+ Context.Items["originalHttpContext"] = Context;
+ _startupResult.Host.BeginRequest();
+ }
}
protected void Application_EndRequest() {
- _host.EndRequest();
+ // Only notify if the host has started up
+ if (_startupResult.Host != null) {
+ _startupResult.Host.EndRequest();
+ }
}
static void MvcSingletons(ContainerBuilder builder) {
@@ -37,6 +108,5 @@ namespace Orchard.Web {
builder.Register(ctx => ModelBinders.Binders).SingleInstance();
builder.Register(ctx => ViewEngines.Engines).SingleInstance();
}
-
}
}
diff --git a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs
index 117178e41..8e3bf32d0 100644
--- a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs
+++ b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs
@@ -182,6 +182,10 @@ namespace Orchard.Setup {
get { return SiteSettingsPartRecord.DefaultPageSize; }
set { throw new NotImplementedException(); }
}
+
+ public string BaseUrl {
+ get { return ""; }
+ }
}
}
}
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Warmup/AdminMenu.cs
new file mode 100644
index 000000000..006cb26e0
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/AdminMenu.cs
@@ -0,0 +1,17 @@
+using Orchard.Localization;
+using Orchard.Security;
+using Orchard.UI.Navigation;
+
+namespace Orchard.Warmup {
+ public class AdminMenu : INavigationProvider {
+ public Localizer T { get; set; }
+ public string MenuName { get { return "admin"; } }
+
+ public void GetNavigation(NavigationBuilder builder) {
+ builder
+ .Add(T("Settings"), menu => menu
+ .Add(T("Warmup" ), "10.0", item => item.Action("Index", "Admin", new { area = "Orchard.Warmup" }).Permission(StandardPermissions.SiteOwner))
+ );
+ }
+ }
+}
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Commands/WarmupCommands.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Commands/WarmupCommands.cs
new file mode 100644
index 000000000..16ebaaf9e
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Commands/WarmupCommands.cs
@@ -0,0 +1,29 @@
+using Orchard.Commands;
+using Orchard.Warmup.Services;
+
+namespace Orchard.Warmup.Commands {
+ public class WarmupCommands : DefaultOrchardCommandHandler {
+ private readonly IWarmupUpdater _warmupUpdater;
+
+ [OrchardSwitch]
+ public bool Force { get; set; }
+
+ public WarmupCommands(IWarmupUpdater warmupUpdater) {
+ _warmupUpdater = warmupUpdater;
+ }
+
+ [CommandName("warmup generate")]
+ [CommandHelp("warmup generate [/Force:true] \r\n\t Generates all the static pages for the warmup feature.")]
+ [OrchardSwitches("Force")]
+ public string Generate() {
+ if(Force) {
+ _warmupUpdater.Generate();
+ }
+ else {
+ _warmupUpdater.EnsureGenerate();
+ }
+
+ return "Generation finished";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Controllers/AdminController.cs
new file mode 100644
index 000000000..d5abdeb6e
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Controllers/AdminController.cs
@@ -0,0 +1,78 @@
+using System.Web.Mvc;
+using Orchard.ContentManagement;
+using Orchard.Core.Contents.Controllers;
+using Orchard.Localization;
+using Orchard.Security;
+using Orchard.Warmup.Models;
+using Orchard.UI.Notify;
+using Orchard.Warmup.Services;
+
+namespace Orchard.Warmup.Controllers {
+ public class AdminController : Controller, IUpdateModel {
+ private readonly IWarmupUpdater _warmupUpdater;
+
+ public AdminController(IOrchardServices services, IWarmupUpdater warmupUpdater) {
+ _warmupUpdater = warmupUpdater;
+ Services = services;
+
+ T = NullLocalizer.Instance;
+ }
+
+ public IOrchardServices Services { get; set; }
+ public Localizer T { get; set; }
+
+ public ActionResult Index() {
+ if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to manage settings")))
+ return new HttpUnauthorizedResult();
+
+ var warmupPart = Services.WorkContext.CurrentSite.As();
+ return View(warmupPart);
+ }
+
+ [FormValueRequired("submit")]
+ [HttpPost, ActionName("Index")]
+ public ActionResult IndexPost() {
+ if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to manage settings")))
+ return new HttpUnauthorizedResult();
+
+ var warmupPart = Services.WorkContext.CurrentSite.As();
+
+ if(TryUpdateModel(warmupPart)) {
+ Services.Notifier.Information(T("Warmup updated successfully."));
+ }
+
+ if (warmupPart.Scheduled) {
+ if (warmupPart.Delay <= 0) {
+ AddModelError("Delay", T("Delay must be greater than zero."));
+ }
+ }
+
+ return View(warmupPart);
+ }
+
+ [FormValueRequired("submit.Generate")]
+ [HttpPost, ActionName("Index")]
+ public ActionResult IndexPostGenerate() {
+ var result = IndexPost();
+
+ if (ModelState.IsValid) {
+ _warmupUpdater.Generate();
+ Services.Notifier.Information(T("Static pages have been generated."));
+ }
+
+ return result;
+ }
+
+ bool IUpdateModel.TryUpdateModel(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) {
+ return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
+ }
+
+ void IUpdateModel.AddModelError(string key, LocalizedString errorMessage) {
+ ModelState.AddModelError(key, errorMessage.ToString());
+ }
+
+ public void AddModelError(string key, LocalizedString errorMessage) {
+ ModelState.AddModelError(key, errorMessage.ToString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Handlers/WarmupContentHandler.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Handlers/WarmupContentHandler.cs
new file mode 100644
index 000000000..27544c9f9
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Handlers/WarmupContentHandler.cs
@@ -0,0 +1,28 @@
+using Orchard.ContentManagement.Handlers;
+using Orchard.ContentManagement;
+using Orchard.Warmup.Models;
+using Orchard.Warmup.Services;
+
+namespace Orchard.Warmup.Handlers {
+ ///
+ /// Intercepts the ContentHandler events to create warmup static pages
+ /// whenever some content is published
+ ///
+ public class WarmupContentHandler : ContentHandler {
+ private readonly IOrchardServices _orchardServices;
+ private readonly IWarmupUpdater _warmupUpdater;
+
+ public WarmupContentHandler(IOrchardServices orchardServices, IWarmupUpdater warmupUpdater) {
+ _orchardServices = orchardServices;
+ _warmupUpdater = warmupUpdater;
+
+ OnPublished(Generate);
+ }
+
+ void Generate(PublishContentContext context, ContentPart part) {
+ if(_orchardServices.WorkContext.CurrentSite.As().OnPublish) {
+ _warmupUpdater.Generate();
+ }
+ }
+ }
+}
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Handlers/WarmupSettingsPartHandler.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Handlers/WarmupSettingsPartHandler.cs
new file mode 100644
index 000000000..37456d4bc
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Handlers/WarmupSettingsPartHandler.cs
@@ -0,0 +1,14 @@
+using JetBrains.Annotations;
+using Orchard.Data;
+using Orchard.ContentManagement.Handlers;
+using Orchard.Warmup.Models;
+
+namespace Orchard.Warmup.Handlers {
+ [UsedImplicitly]
+ public class WarmupSettingsPartHandler : ContentHandler {
+ public WarmupSettingsPartHandler(IRepository repository) {
+ Filters.Add(new ActivatingFilter("Site"));
+ Filters.Add(StorageFilter.For(repository));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Migrations.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Migrations.cs
new file mode 100644
index 000000000..c12b4ee63
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Migrations.cs
@@ -0,0 +1,18 @@
+using Orchard.Data.Migration;
+
+namespace Orchard.Warmup {
+ public class Migrations : DataMigrationImpl {
+ public int Create() {
+ SchemaBuilder.CreateTable("WarmupSettingsPartRecord",
+ table => table
+ .ContentPartRecord()
+ .Column("Urls", column => column.Unlimited())
+ .Column("Scheduled")
+ .Column("Delay")
+ .Column("OnPublish")
+ );
+
+ return 1;
+ }
+ }
+}
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Models/WarmupSettingsPart.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Models/WarmupSettingsPart.cs
new file mode 100644
index 000000000..fab011abe
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Models/WarmupSettingsPart.cs
@@ -0,0 +1,26 @@
+using Orchard.ContentManagement;
+
+namespace Orchard.Warmup.Models {
+ public class WarmupSettingsPart : ContentPart {
+
+ public string Urls {
+ get { return Record.Urls; }
+ set { Record.Urls = value; }
+ }
+
+ public bool Scheduled {
+ get { return Record.Scheduled; }
+ set { Record.Scheduled = value; }
+ }
+
+ public int Delay {
+ get { return Record.Delay; }
+ set { Record.Delay = value; }
+ }
+
+ public bool OnPublish {
+ get { return Record.OnPublish; }
+ set { Record.OnPublish = value; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Models/WarmupSettingsPartRecord.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Models/WarmupSettingsPartRecord.cs
new file mode 100644
index 000000000..410c7a6bd
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Models/WarmupSettingsPartRecord.cs
@@ -0,0 +1,16 @@
+using Orchard.ContentManagement.Records;
+using Orchard.Data.Conventions;
+
+namespace Orchard.Warmup.Models {
+ public class WarmupSettingsPartRecord : ContentPartRecord {
+ public WarmupSettingsPartRecord() {
+ Delay = 90;
+ }
+
+ [StringLengthMax]
+ public virtual string Urls { get; set; }
+ public virtual bool Scheduled { get; set; }
+ public virtual int Delay { get; set; }
+ public virtual bool OnPublish { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Module.txt b/src/Orchard.Web/Modules/Orchard.Warmup/Module.txt
new file mode 100644
index 000000000..1fe630fc7
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Module.txt
@@ -0,0 +1,12 @@
+Name: Orchard.Warmup
+AntiForgery: enabled
+Author: The Orchard Team
+Website: http://orchardproject.net
+Version: 1.0
+OrchardVersion: 1.0
+Description: Provides a mecanism to generate a static version of pages for being used during application warm up.
+Features:
+ Orchard.Warmup:
+ Description: Generates the static version of specific pages periodically.
+ Name: Warmup
+ Category: Hosting
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Orchard.Warmup.csproj b/src/Orchard.Web/Modules/Orchard.Warmup/Orchard.Warmup.csproj
new file mode 100644
index 000000000..e9be6c5f1
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Orchard.Warmup.csproj
@@ -0,0 +1,142 @@
+
+
+
+ Debug
+ AnyCPU
+ 9.0.30729
+ 2.0
+ {9CD5C81F-5828-4384-8474-2E2BE71D5EDD}
+ {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}
+ Library
+ Properties
+ Orchard.Warmup
+ Orchard.Warmup
+ v4.0
+ false
+
+
+ 3.5
+
+
+ false
+
+
+ true
+ full
+ false
+ bin\
+ DEBUG;TRACE
+ prompt
+ 4
+ AllRules.ruleset
+
+
+ pdbonly
+ true
+ bin\
+ TRACE
+ prompt
+ 4
+ AllRules.ruleset
+
+
+
+
+
+
+ 3.5
+
+
+
+ False
+ ..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}
+ Orchard.Framework
+
+
+ {9916839C-39FC-4CEB-A5AF-89CA7E87119F}
+ Orchard.Core
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(ProjectDir)\..\Manifests
+
+
+
+
+
+
+
+
+
+
+
+ False
+ True
+ 45979
+ /
+
+
+ False
+ True
+ http://orchard.codeplex.com
+ False
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Placement.info b/src/Orchard.Web/Modules/Orchard.Warmup/Placement.info
new file mode 100644
index 000000000..dc7ef4eab
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Placement.info
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Properties/AssemblyInfo.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..b655c3bbb
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Properties/AssemblyInfo.cs
@@ -0,0 +1,34 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Orchard.Warmup")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyProduct("Orchard")]
+[assembly: AssemblyCopyright("")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("3484b5d3-de81-4a46-bfa6-c1d02a029184")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Revision and Build Numbers
+// by using the '*' as shown below:
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Scripts/Web.config b/src/Orchard.Web/Modules/Orchard.Warmup/Scripts/Web.config
new file mode 100644
index 000000000..178ff35ba
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Scripts/Web.config
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Services/IWarmupUpdater.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Services/IWarmupUpdater.cs
new file mode 100644
index 000000000..c133d56b8
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Services/IWarmupUpdater.cs
@@ -0,0 +1,13 @@
+namespace Orchard.Warmup.Services {
+ public interface IWarmupUpdater : IDependency {
+ ///
+ /// Forces a regeneration of all static pages
+ ///
+ void Generate();
+
+ ///
+ /// Generates static pages if needed
+ ///
+ void EnsureGenerate();
+ }
+}
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Services/IWebDownloader.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Services/IWebDownloader.cs
new file mode 100644
index 000000000..6911494db
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Services/IWebDownloader.cs
@@ -0,0 +1,13 @@
+
+using System.Net;
+
+namespace Orchard.Warmup.Services {
+ public class DownloadResult {
+ public HttpStatusCode StatusCode { get; set; }
+ public string Content { get; set; }
+ }
+
+ public interface IWebDownloader : IDependency {
+ DownloadResult Download(string url);
+ }
+}
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Services/SettingsBanner.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Services/SettingsBanner.cs
new file mode 100644
index 000000000..b51da54f0
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Services/SettingsBanner.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using Orchard.ContentManagement;
+using Orchard.Environment.Configuration;
+using Orchard.Localization;
+using Orchard.UI.Admin.Notification;
+using Orchard.UI.Notify;
+
+namespace Orchard.Warmup.Services {
+ public class SettingsBanner: INotificationProvider {
+ private readonly IOrchardServices _orchardServices;
+
+ public SettingsBanner(IOrchardServices orchardServices) {
+ _orchardServices = orchardServices;
+ T = NullLocalizer.Instance;
+ }
+
+ public Localizer T { get; set; }
+
+ public IEnumerable GetNotifications() {
+ if ( string.IsNullOrWhiteSpace(_orchardServices.WorkContext.CurrentSite.BaseUrl)) {
+ yield return new NotifyEntry { Message = T("The Warmup feature needs the Base Url site setting to be set." ), Type = NotifyType.Warning };
+ }
+ }
+ }
+}
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Services/WarmupTask.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Services/WarmupTask.cs
new file mode 100644
index 000000000..549ea5199
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Services/WarmupTask.cs
@@ -0,0 +1,25 @@
+using Orchard.ContentManagement;
+using Orchard.Tasks;
+using Orchard.Warmup.Models;
+
+namespace Orchard.Warmup.Services {
+ public class WarmupTask : IBackgroundTask {
+ private readonly IOrchardServices _orchardServices;
+ private readonly IWarmupUpdater _warmupUpdater;
+
+ public WarmupTask(IOrchardServices orchardServices, IWarmupUpdater warmupUpdater) {
+ _orchardServices = orchardServices;
+ _warmupUpdater = warmupUpdater;
+ }
+
+ public void Sweep() {
+ var part = _orchardServices.WorkContext.CurrentSite.As();
+
+ if (!part.Scheduled) {
+ return;
+ }
+
+ _warmupUpdater.EnsureGenerate();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Services/WarmupUpdater.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Services/WarmupUpdater.cs
new file mode 100644
index 000000000..580bee76b
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Services/WarmupUpdater.cs
@@ -0,0 +1,151 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Web;
+using System.Xml;
+using Orchard.ContentManagement;
+using Orchard.Environment.Warmup;
+using Orchard.FileSystems.AppData;
+using Orchard.FileSystems.LockFile;
+using Orchard.Logging;
+using Orchard.Services;
+using Orchard.Warmup.Models;
+
+namespace Orchard.Warmup.Services {
+ public class WarmupUpdater : IWarmupUpdater {
+ private readonly IOrchardServices _orchardServices;
+ private readonly ILockFileManager _lockFileManager;
+ private readonly IClock _clock;
+ private readonly IAppDataFolder _appDataFolder;
+ private readonly IWebDownloader _webDownloader;
+ private const string BaseFolder = "Warmup";
+ private const string WarmupFilename = "warmup.txt";
+ private readonly string _lockFilename;
+
+ public WarmupUpdater(
+ IOrchardServices orchardServices,
+ ILockFileManager lockFileManager,
+ IClock clock,
+ IAppDataFolder appDataFolder,
+ IWebDownloader webDownloader) {
+ _orchardServices = orchardServices;
+ _lockFileManager = lockFileManager;
+ _clock = clock;
+ _appDataFolder = appDataFolder;
+ _webDownloader = webDownloader;
+ _lockFilename = _appDataFolder.Combine(BaseFolder, WarmupFilename + ".lock");
+
+ Logger = NullLogger.Instance;
+ }
+
+ public ILogger Logger { get; set; }
+
+ public void EnsureGenerate() {
+ var baseUrl = _orchardServices.WorkContext.CurrentSite.BaseUrl;
+ var part = _orchardServices.WorkContext.CurrentSite.As();
+
+ // do nothing while the base url setting is not defined, or if there is no page defined
+ if (String.IsNullOrWhiteSpace(baseUrl) || String.IsNullOrWhiteSpace(part.Urls)) {
+ return;
+ }
+
+ // prevent multiple appdomains from rebuilding the static page concurrently (e.g., command line)
+ ILockFile lockFile = null;
+ if (!_lockFileManager.TryAcquireLock(_lockFilename, ref lockFile)) {
+ return;
+ }
+
+ using (lockFile) {
+
+ // check if we need to regenerate the pages by reading the last time it has been done
+ // 1- if the warmup file doesn't exists, generate the pages
+ // 2- otherwise, if the scheduled generation option is on, check if the delay is over
+ var warmupPath = _appDataFolder.Combine(BaseFolder, WarmupFilename);
+ if(_appDataFolder.FileExists(warmupPath)) {
+ try {
+ var warmupContent = _appDataFolder.ReadFile(warmupPath);
+ var expired = XmlConvert.ToDateTimeOffset(warmupContent).AddMinutes(part.Delay);
+ if (expired > _clock.UtcNow) {
+ return;
+ }
+ }
+ catch {
+ // invalid file, delete continue processing
+ _appDataFolder.DeleteFile(warmupPath);
+ }
+ }
+
+ // delete existing static page files
+ foreach (var filename in _appDataFolder.ListFiles(BaseFolder)) {
+ var prefix = _appDataFolder.Combine(BaseFolder, "http");
+
+ // delete only static page files
+ if (!filename.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) {
+ continue;
+ }
+
+ try {
+ _appDataFolder.DeleteFile(filename);
+ }
+ catch(Exception e) {
+ // ignore files which could not be deleted
+ Logger.Error(e, "Could not delete file {0}", filename);
+ }
+ }
+
+ // loop over every relative url to generate the contents
+ using (var urlReader = new StringReader(part.Urls)) {
+ string relativeUrl;
+ while (null != (relativeUrl = urlReader.ReadLine())) {
+ string url = null;
+ relativeUrl = relativeUrl.Trim();
+
+ try {
+ url = VirtualPathUtility.RemoveTrailingSlash(baseUrl) + relativeUrl;
+ var download = _webDownloader.Download(url);
+
+ if (download != null && download.StatusCode == HttpStatusCode.OK) {
+ var filename = WarmupUtility.EncodeUrl(url);
+ var path = _appDataFolder.Combine(BaseFolder, filename);
+ _appDataFolder.CreateFile(path, download.Content);
+
+ // if the base url contains http://www, then also render the www-less one
+
+ if (url.StartsWith("http://www.", StringComparison.OrdinalIgnoreCase)) {
+ url = "http://" + url.Substring("http://www.".Length);
+ filename = WarmupUtility.EncodeUrl(url);
+ path = _appDataFolder.Combine(BaseFolder, filename);
+ _appDataFolder.CreateFile(path, download.Content);
+ }
+
+ }
+ }
+ catch (Exception e) {
+ Logger.Error(e, "Could not extract warmup page content for: ", url);
+ }
+ }
+ }
+
+ // finally write the time the generation has been executed
+ _appDataFolder.CreateFile(warmupPath, XmlConvert.ToString(_clock.UtcNow, XmlDateTimeSerializationMode.Utc));
+ }
+ }
+
+ public void Generate() {
+ // prevent multiple appdomains from rebuilding the static page concurrently (e.g., command line)
+ ILockFile lockFile = null;
+ if (!_lockFileManager.TryAcquireLock(_lockFilename, ref lockFile)) {
+ return;
+ }
+
+ using (lockFile) {
+ var warmupPath = _appDataFolder.Combine(BaseFolder, WarmupFilename);
+ if (_appDataFolder.FileExists(warmupPath)) {
+ _appDataFolder.DeleteFile(warmupPath);
+ }
+ }
+
+ EnsureGenerate();
+ }
+ }
+}
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Services/WebDownloader.cs b/src/Orchard.Web/Modules/Orchard.Warmup/Services/WebDownloader.cs
new file mode 100644
index 000000000..012089c03
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Services/WebDownloader.cs
@@ -0,0 +1,49 @@
+using System;
+using System.IO;
+using System.Net;
+using Orchard.Logging;
+
+namespace Orchard.Warmup.Services {
+ public class WebDownloader : IWebDownloader {
+ public WebDownloader() {
+ Logger = NullLogger.Instance;
+ }
+
+ public ILogger Logger { get; set; }
+
+ public DownloadResult Download(string url) {
+ if(String.IsNullOrWhiteSpace(url)) {
+ return null;
+ }
+
+ try {
+ var request = WebRequest.Create(url) as HttpWebRequest;
+ if (request != null) {
+ using (var response = request.GetResponse() as HttpWebResponse) {
+ if (response != null) {
+ using (var stream = response.GetResponseStream()) {
+ if (stream != null) {
+ using (var sr = new StreamReader(stream)) {
+ return new DownloadResult {Content = sr.ReadToEnd(), StatusCode = response.StatusCode};
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ catch (WebException e) {
+ if(e.Response as HttpWebResponse != null) {
+ return new DownloadResult { StatusCode = ((HttpWebResponse)e.Response).StatusCode };
+ }
+
+ return null;
+ }
+ catch(Exception e) {
+ Logger.Error(e, "An error occured while downloading url: {0}", url);
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Styles/Web.config b/src/Orchard.Web/Modules/Orchard.Warmup/Styles/Web.config
new file mode 100644
index 000000000..178ff35ba
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Styles/Web.config
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Views/Admin/Index.cshtml b/src/Orchard.Web/Modules/Orchard.Warmup/Views/Admin/Index.cshtml
new file mode 100644
index 000000000..a0d0fd5bd
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Views/Admin/Index.cshtml
@@ -0,0 +1,37 @@
+@model Orchard.Warmup.Models.WarmupSettingsPart
+@using Orchard.Utility.Extensions;
+@using Orchard.Warmup.Models;
+
+@{ Layout.Title = T("Warmup").ToString(); }
+
+@using (Html.BeginFormAntiForgeryPost()) {
+ @Html.ValidationSummary()
+
+
+
+
+}
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Views/EditorTemplates/Parts.Warmup.SiteSettings.cshtml b/src/Orchard.Web/Modules/Orchard.Warmup/Views/EditorTemplates/Parts.Warmup.SiteSettings.cshtml
new file mode 100644
index 000000000..6fb7c7a40
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Views/EditorTemplates/Parts.Warmup.SiteSettings.cshtml
@@ -0,0 +1,28 @@
+@model Orchard.Warmup.Models.WarmupSettingsPartRecord
+@using Orchard.Utility.Extensions;
+@using Orchard.Warmup.Models;
+
+
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Views/Web.config b/src/Orchard.Web/Modules/Orchard.Warmup/Views/Web.config
new file mode 100644
index 000000000..b7d215131
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Views/Web.config
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Orchard.Web/Modules/Orchard.Warmup/Web.config b/src/Orchard.Web/Modules/Orchard.Warmup/Web.config
new file mode 100644
index 000000000..5884c5879
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Warmup/Web.config
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Orchard.Web/Modules/Orchard.jQuery/Orchard.jQuery.csproj b/src/Orchard.Web/Modules/Orchard.jQuery/Orchard.jQuery.csproj
index 748ab92fa..058377aa9 100644
--- a/src/Orchard.Web/Modules/Orchard.jQuery/Orchard.jQuery.csproj
+++ b/src/Orchard.Web/Modules/Orchard.jQuery/Orchard.jQuery.csproj
@@ -18,6 +18,7 @@
3.5
+ falsetrue
@@ -48,11 +49,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Orchard.Web/Orchard.Web.csproj b/src/Orchard.Web/Orchard.Web.csproj
index ec7b050fa..db45cf2f6 100644
--- a/src/Orchard.Web/Orchard.Web.csproj
+++ b/src/Orchard.Web/Orchard.Web.csproj
@@ -18,6 +18,7 @@
3.5
+ truetrue
@@ -199,8 +200,7 @@
False30320/OrchardLocal
-
-
+ http://localhost:30320/OrchardLocalFalseFalse
diff --git a/src/Orchard.sln b/src/Orchard.sln
index 8d35901cb..57da0789e 100644
--- a/src/Orchard.sln
+++ b/src/Orchard.sln
@@ -112,6 +112,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Recipes", "Orchard.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.ImportExport", "Orchard.Web\Modules\Orchard.ImportExport\Orchard.ImportExport.csproj", "{FE5C5947-D2D5-42C5-992A-13D672946135}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Warmup", "Orchard.Web\Modules\Orchard.WarmUp\Orchard.Warmup.csproj", "{9CD5C81F-5828-4384-8474-2E2BE71D5EDD}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
CodeCoverage|Any CPU = CodeCoverage|Any CPU
@@ -595,6 +597,13 @@ Global
{FE5C5947-D2D5-42C5-992A-13D672946135}.FxCop|Any CPU.Build.0 = Release|Any CPU
{FE5C5947-D2D5-42C5-992A-13D672946135}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE5C5947-D2D5-42C5-992A-13D672946135}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9CD5C81F-5828-4384-8474-2E2BE71D5EDD}.CodeCoverage|Any CPU.ActiveCfg = Release|Any CPU
+ {9CD5C81F-5828-4384-8474-2E2BE71D5EDD}.Coverage|Any CPU.ActiveCfg = Release|Any CPU
+ {9CD5C81F-5828-4384-8474-2E2BE71D5EDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9CD5C81F-5828-4384-8474-2E2BE71D5EDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9CD5C81F-5828-4384-8474-2E2BE71D5EDD}.FxCop|Any CPU.ActiveCfg = Release|Any CPU
+ {9CD5C81F-5828-4384-8474-2E2BE71D5EDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9CD5C81F-5828-4384-8474-2E2BE71D5EDD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -634,6 +643,7 @@ Global
{43D0EC0B-1955-4566-8D31-7B9102DA1703} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
{FC1D74E8-7A4D-48F4-83DE-95C6173780C4} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
{FE5C5947-D2D5-42C5-992A-13D672946135} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
+ {9CD5C81F-5828-4384-8474-2E2BE71D5EDD} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
{ABC826D4-2FA1-4F2F-87DE-E6095F653810} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA}
{F112851D-B023-4746-B6B1-8D2E5AD8F7AA} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA}
{6CB3EB30-F725-45C0-9742-42599BA8E8D2} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA}
diff --git a/src/Orchard/Environment/Warmup/StartupResult.cs b/src/Orchard/Environment/Warmup/StartupResult.cs
new file mode 100644
index 000000000..0a1400f90
--- /dev/null
+++ b/src/Orchard/Environment/Warmup/StartupResult.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace Orchard.Environment.Warmup {
+ public class StartupResult {
+ public IOrchardHost Host { get; set; }
+ public Exception Error { get; set; }
+ }
+}
diff --git a/src/Orchard/Environment/Warmup/WarmupUtility.cs b/src/Orchard/Environment/Warmup/WarmupUtility.cs
new file mode 100644
index 000000000..e13003fd4
--- /dev/null
+++ b/src/Orchard/Environment/Warmup/WarmupUtility.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Orchard.Environment.Warmup {
+ public static class WarmupUtility {
+ private const string EncodingPattern = "[^a-z0-9]";
+
+ public static string EncodeUrl(string url) {
+ if(String.IsNullOrWhiteSpace(url)) {
+ throw new ArgumentException("url can't be empty");
+ }
+
+ return Regex.Replace(url.ToLower(), EncodingPattern, m => "_" + Encoding.UTF8.GetBytes(m.Value).Select(b => b.ToString("X")).Aggregate((a, b) => a + b));
+ }
+
+ }
+}
diff --git a/src/Orchard/FileSystems/LockFile/DefaultLockFileManager.cs b/src/Orchard/FileSystems/LockFile/DefaultLockFileManager.cs
index 117f6f03a..30198072c 100644
--- a/src/Orchard/FileSystems/LockFile/DefaultLockFileManager.cs
+++ b/src/Orchard/FileSystems/LockFile/DefaultLockFileManager.cs
@@ -58,8 +58,7 @@ namespace Orchard.FileSystems.LockFile {
var content = _appDataFolder.ReadFile(path);
DateTime creationUtc;
- if (DateTime.TryParse(content, out creationUtc))
- {
+ if (DateTime.TryParse(content, out creationUtc)) {
// if expired the file is not removed
// it should be automatically as there is a finalizer in LockFile
// or the next taker can do it, unless it also fails, again
diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj
index 4979e9f29..cd1120190 100644
--- a/src/Orchard/Orchard.Framework.csproj
+++ b/src/Orchard/Orchard.Framework.csproj
@@ -183,6 +183,8 @@
+
+
diff --git a/src/Orchard/Settings/ISite.cs b/src/Orchard/Settings/ISite.cs
index f215673e7..e37d47e63 100644
--- a/src/Orchard/Settings/ISite.cs
+++ b/src/Orchard/Settings/ISite.cs
@@ -13,5 +13,6 @@ namespace Orchard.Settings {
string SiteCulture { get; set; }
ResourceDebugMode ResourceDebugMode { get; set; }
int PageSize { get; set; }
+ string BaseUrl { get; }
}
}