Implementing the Warmup module

- Global.asax.cs looks for static pages in ~\App_Data\Warmup
- Orchard.Warmup module contains methods to generate those pages based on
scheduled times, and content publishing

--HG--
branch : dev
This commit is contained in:
Sebastien Ros
2011-03-17 11:06:23 -07:00
parent c4a2de09b6
commit 15dbaaf355
48 changed files with 1518 additions and 44 deletions

View File

@@ -169,6 +169,8 @@
<Compile Include="Users\ShellSettingsUtility.cs" />
<Compile Include="Values.cs" />
<Compile Include="Users\Services\MembershipServiceTests.cs" />
<Compile Include="Warmup\WebDownloaderTests.cs" />
<Compile Include="Warmup\WarmupUpdaterTests.cs" />
<Compile Include="Widgets\RuleEngine\UrlRuleProviderTest.cs" />
<Compile Include="Widgets\Services\WidgetsServiceTest.cs" />
<Compile Include="Widgets\WidgetsTests.cs" />
@@ -254,6 +256,10 @@
<Project>{79AED36E-ABD0-4747-93D3-8722B042454B}</Project>
<Name>Orchard.Users</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Web\Modules\Orchard.WarmUp\Orchard.Warmup.csproj">
<Project>{9CD5C81F-5828-4384-8474-2E2BE71D5EDD}</Project>
<Name>Orchard.Warmup</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Web\Modules\Orchard.Widgets\Orchard.Widgets.csproj">
<Project>{194D3CCC-1153-474D-8176-FDE8D7D0D0BD}</Project>
<Name>Orchard.Widgets</Name>

View File

@@ -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<IWebDownloader> _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<IWebDownloader>();
_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<IAppDataFolder>();
builder.RegisterInstance(_orchardServices).As<IOrchardServices>();
builder.RegisterType<DefaultLockFileManager>().As<ILockFileManager>();
builder.RegisterType<WarmupUpdater>().As<IWarmupUpdater>();
builder.RegisterType<StubClock>().As<IClock>();
builder.RegisterInstance(_clock = new StubClock()).As<IClock>();
builder.RegisterInstance(_webDownloader.Object).As<IWebDownloader>();
_container = builder.Build();
_lockFileManager = _container.Resolve<ILockFileManager>();
_warmupUpdater = _container.Resolve<IWarmupUpdater>();
_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<string>(x => x == _appDataFolder.Combine(WarmupFolder, "warmup.txt")));
Assert.That(files, Has.Some.Matches<string>(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/"))));
Assert.That(files, Has.Some.Matches<string>(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<string>(x => x == _appDataFolder.Combine(WarmupFolder, "warmup.txt")));
Assert.That(files, Has.Some.Matches<string>(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/"))));
Assert.That(files, Has.None.Matches<string>(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<string>(x => x == _appDataFolder.Combine(WarmupFolder, "warmup.txt")));
Assert.That(files, Has.Some.Matches<string>(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<string>(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<string>(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<string>(x => x == _appDataFolder.Combine(WarmupFolder, "warmup.txt")));
Assert.That(files, Has.Some.Matches<string>(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://www.orchardproject.net/"))));
Assert.That(files, Has.Some.Matches<string>(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://www.orchardproject.net/About"))));
Assert.That(files, Has.Some.Matches<string>(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/"))));
Assert.That(files, Has.Some.Matches<string>(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"));
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<ArgumentException>(() => WarmupUtility.EncodeUrl(""));
Assert.Throws<ArgumentException>(() => 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"));
}
}
}

View File

@@ -239,6 +239,7 @@
<Compile Include="Environment\RunningShellTableTests.cs" />
<Compile Include="Environment\StubHostEnvironment.cs" />
<Compile Include="Environment\Utility\Build.cs" />
<Compile Include="Environment\WarmUp\WarmUpUtilityTests.cs" />
<Compile Include="FileSystems\AppData\AppDataFolderTests.cs" />
<Compile Include="Environment\Configuration\DefaultTenantManagerTests.cs" />
<Compile Include="Environment\DefaultCompositionStrategyTests.cs" />

View File

@@ -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 {

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}

View File

@@ -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));
}

View File

@@ -95,5 +95,14 @@ namespace Orchard.Core.Settings {
return 1;
}
public int UpdateFrom1() {
SchemaBuilder.AlterTable("SiteSettingsPartRecord",
table => table
.AddColumn<string>("BaseUrl", c => c.WithLength(255))
);
return 2;
}
}
}

View File

@@ -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; }
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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<string> 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<SiteSettingsPart>().Record.SuperUser; }
set { Site.As<SiteSettingsPart>().Record.SuperUser = value; }
get { return Site.SuperUser; }
set { Site.SuperUser = value; }
}
public ResourceDebugMode ResourceDebugMode {
get { return Site.As<SiteSettingsPart>().ResourceDebugMode; }
set { Site.As<SiteSettingsPart>().ResourceDebugMode = value; }
get { return Site.ResourceDebugMode; }
set { Site.ResourceDebugMode = value; }
}
public int PageSize {
get { return Site.As<SiteSettingsPart>().PageSize; }
set { Site.As<SiteSettingsPart>().PageSize = value; }
get { return Site.PageSize; }
set { Site.PageSize = value; }
}
public string BaseUrl {
get { return Site.BaseUrl; }
set { Site.BaseUrl = value; }
}
}
}

View File

@@ -38,7 +38,13 @@
</div>
<div>
<label for="DefaultPageSize">@T("Default number of items per page")</label>
@Html.TextBoxFor(m => m.PageSize, new { @class = "textMedium" })
@Html.TextBoxFor(m => m.PageSize, new { @class = "text-small" })
<span class="hint">@T("Determines the default number of items that are shown per page.")</span>
</div>
<div>
<label for="@Html.FieldIdFor(m => m.BaseUrl)">@T("Base url ")</label>
@Html.TextBoxFor(m => m.BaseUrl, new { @class = "textMedium" })
<span class="hint">@T("Enter the fully qualified base url of your website.")</span>
<span class="hint">@T("e.g., http://localhost:30320/orchardlocal, http://www.yourdomain.com")</span>
</div>
</fieldset>

View File

@@ -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();
/// <summary>
/// Initializes Orchard's Host in a separate thread
/// </summary>
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();
}
}
}

View File

@@ -182,6 +182,10 @@ namespace Orchard.Setup {
get { return SiteSettingsPartRecord.DefaultPageSize; }
set { throw new NotImplementedException(); }
}
public string BaseUrl {
get { return ""; }
}
}
}
}

View File

@@ -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))
);
}
}
}

View File

@@ -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";
}
}
}

View File

@@ -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<WarmupSettingsPart>();
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<WarmupSettingsPart>();
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>(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());
}
}
}

View File

@@ -0,0 +1,28 @@
using Orchard.ContentManagement.Handlers;
using Orchard.ContentManagement;
using Orchard.Warmup.Models;
using Orchard.Warmup.Services;
namespace Orchard.Warmup.Handlers {
/// <summary>
/// Intercepts the ContentHandler events to create warmup static pages
/// whenever some content is published
/// </summary>
public class WarmupContentHandler : ContentHandler {
private readonly IOrchardServices _orchardServices;
private readonly IWarmupUpdater _warmupUpdater;
public WarmupContentHandler(IOrchardServices orchardServices, IWarmupUpdater warmupUpdater) {
_orchardServices = orchardServices;
_warmupUpdater = warmupUpdater;
OnPublished<ContentPart>(Generate);
}
void Generate(PublishContentContext context, ContentPart part) {
if(_orchardServices.WorkContext.CurrentSite.As<WarmupSettingsPart>().OnPublish) {
_warmupUpdater.Generate();
}
}
}
}

View File

@@ -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<WarmupSettingsPartRecord> repository) {
Filters.Add(new ActivatingFilter<WarmupSettingsPart>("Site"));
Filters.Add(StorageFilter.For(repository));
}
}
}

View File

@@ -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<string>("Urls", column => column.Unlimited())
.Column<bool>("Scheduled")
.Column<int>("Delay")
.Column<bool>("OnPublish")
);
return 1;
}
}
}

View File

@@ -0,0 +1,26 @@
using Orchard.ContentManagement;
namespace Orchard.Warmup.Models {
public class WarmupSettingsPart : ContentPart<WarmupSettingsPartRecord> {
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; }
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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

View File

@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{9CD5C81F-5828-4384-8474-2E2BE71D5EDD}</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Orchard.Warmup</RootNamespace>
<AssemblyName>Orchard.Warmup</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<MvcBuildViews>false</MvcBuildViews>
<FileUpgradeFlags>
</FileUpgradeFlags>
<OldToolsVersion>3.5</OldToolsVersion>
<UpgradeBackupLocation />
<TargetFrameworkProfile />
<UseIISExpress>false</UseIISExpress>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.ComponentModel.DataAnnotations">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll</HintPath>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Web.Abstractions" />
<Reference Include="System.Web.Routing" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Content Include="Web.config" />
<Content Include="Views\Web.config" />
<Content Include="Scripts\Web.config" />
<Content Include="Styles\Web.config" />
<Content Include="Properties\AssemblyInfo.cs" />
<Content Include="Module.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj">
<Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project>
<Name>Orchard.Framework</Name>
</ProjectReference>
<ProjectReference Include="..\..\Core\Orchard.Core.csproj">
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
<Name>Orchard.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="AdminMenu.cs" />
<Compile Include="Commands\WarmupCommands.cs" />
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Handlers\WarmupContentHandler.cs" />
<Compile Include="Handlers\WarmupSettingsPartHandler.cs" />
<Compile Include="Migrations.cs" />
<Compile Include="Models\WarmupSettingsPart.cs" />
<Compile Include="Models\WarmupSettingsPartRecord.cs" />
<Compile Include="Services\WebDownloader.cs" />
<Compile Include="Services\IWarmupUpdater.cs" />
<Compile Include="Services\IWebDownloader.cs" />
<Compile Include="Services\SettingsBanner.cs" />
<Compile Include="Services\WarmupTask.cs" />
<Compile Include="Services\WarmupUpdater.cs" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Content Include="Views\EditorTemplates\Parts.Warmup.SiteSettings.cshtml" />
</ItemGroup>
<ItemGroup>
<None Include="Placement.info" />
<Content Include="Views\Admin\Index.cshtml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target> -->
<Target Name="AfterBuild" DependsOnTargets="AfterBuildCompiler">
<PropertyGroup>
<AreasManifestDir>$(ProjectDir)\..\Manifests</AreasManifestDir>
</PropertyGroup>
<!-- If this is an area child project, uncomment the following line:
<CreateAreaManifest AreaName="$(AssemblyName)" AreaType="Child" AreaPath="$(ProjectDir)" ManifestPath="$(AreasManifestDir)" ContentFiles="@(Content)" />
-->
<!-- If this is an area parent project, uncomment the following lines:
<CreateAreaManifest AreaName="$(AssemblyName)" AreaType="Parent" AreaPath="$(ProjectDir)" ManifestPath="$(AreasManifestDir)" ContentFiles="@(Content)" />
<CopyAreaManifests ManifestPath="$(AreasManifestDir)" CrossCopy="false" RenameViews="true" />
-->
</Target>
<Target Name="AfterBuildCompiler" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(ProjectDir)\..\$(ProjectName)" />
</Target>
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>45979</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>
</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>True</UseCustomServer>
<CustomServerUrl>http://orchard.codeplex.com</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
</Project>

View File

@@ -0,0 +1,3 @@
<Placement>
<Place Parts_Warmup_SiteSettings="Content:10"/>
</Placement>

View File

@@ -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")]

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appSettings>
<add key="webpages:Enabled" value="false" />
</appSettings>
<system.web>
<httpHandlers>
<!-- iis6 - for any request in this location, return via managed static file handler -->
<add path="*" verb="*" type="System.Web.StaticFileHandler" />
</httpHandlers>
</system.web>
<system.webServer>
<handlers accessPolicy="Script,Read">
<!--
iis7 - for any request to a file exists on disk, return it via native http module.
accessPolicy 'Script' is to allow for a managed 404 page.
-->
<add name="StaticFile" path="*" verb="*" modules="StaticFileModule" preCondition="integratedMode" resourceType="File" requireAccess="Read" />
</handlers>
</system.webServer>
</configuration>

View File

@@ -0,0 +1,13 @@
namespace Orchard.Warmup.Services {
public interface IWarmupUpdater : IDependency {
/// <summary>
/// Forces a regeneration of all static pages
/// </summary>
void Generate();
/// <summary>
/// Generates static pages if needed
/// </summary>
void EnsureGenerate();
}
}

View File

@@ -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);
}
}

View File

@@ -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<NotifyEntry> 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 };
}
}
}
}

View File

@@ -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<WarmupSettingsPart>();
if (!part.Scheduled) {
return;
}
_warmupUpdater.EnsureGenerate();
}
}
}

View File

@@ -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<WarmupSettingsPart>();
// 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();
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appSettings>
<add key="webpages:Enabled" value="false" />
</appSettings>
<system.web>
<httpHandlers>
<!-- iis6 - for any request in this location, return via managed static file handler -->
<add path="*" verb="*" type="System.Web.StaticFileHandler" />
</httpHandlers>
</system.web>
<system.webServer>
<handlers accessPolicy="Script,Read">
<!--
iis7 - for any request to a file exists on disk, return it via native http module.
accessPolicy 'Script' is to allow for a managed 404 page.
-->
<add name="StaticFile" path="*" verb="*" modules="StaticFileModule" preCondition="integratedMode" resourceType="File" requireAccess="Read" />
</handlers>
</system.webServer>
</configuration>

View File

@@ -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()
<fieldset>
<div>
<label for="@Html.FieldIdFor(m => m.Urls)">@T("Urls for which static warmup pages will be generated")</label>
@Html.TextAreaFor(m => m.Urls, new { @class = "textMedium" })
<span class="hint">@T("This must be a set of relative paths, e.g., /, /About")</span>
</div>
</fieldset>
<fieldset>
<div>
@Html.EditorFor(m => m.Scheduled)
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.Scheduled)">@T("Generate warmup pages periodically")</label>
</div>
<div data-controllerid="@Html.FieldIdFor(m => m.Scheduled)">
@T("Every")
@Html.TextBoxFor(m => m.Delay, new { @class = "text-small" })
@T("minutes")
@Html.ValidationMessage("Delay", "*")
</div>
<div>
@Html.EditorFor(m => m.OnPublish)
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.OnPublish)">@T("Generate warmup pages each time some content is published")</label>
</div>
</fieldset>
<fieldset>
<button class="primaryAction" name="submit" value="@T("Save")" type="submit">@T("Save")</button>
<button class="primaryAction" name="submit.Generate" value="@T("Save and generate")" type="submit">@T("Save and generate")</button>
</fieldset>
}

View File

@@ -0,0 +1,28 @@
@model Orchard.Warmup.Models.WarmupSettingsPartRecord
@using Orchard.Utility.Extensions;
@using Orchard.Warmup.Models;
<fieldset>
<legend>@T("Warmup")</legend>
<div>
<label for="@Html.FieldIdFor(m => m.Urls)">@T("Urls for which static warm up pages will be generated")</label>
@Html.TextAreaFor(m => m.Urls, new { @class = "textMedium" })
@Html.ValidationMessage("Urls", "*")
<span class="hint">@T("This must be a set of virtual paths, e.g., ~/, ~/About")</span>
</div>
<div>
@Html.EditorFor(m => m.Scheduled)
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.Scheduled)">@T("Generate warmup pages periodically")</label>
</div>
<div data-controllerid="@Html.FieldIdFor(m => m.Scheduled)>
<label for="@Html.FieldIdFor(m => m.Urls)">@T("Delay to generate pages")</label>
@Html.TextBoxFor(m => m.Delay, new { @class = "" }) @T("minutes")
@Html.ValidationMessage("Delay", "*")
</div>
<div>
@Html.EditorFor(m => m.OnPublish)
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.OnPublish)">@T("Generate warmup pages each time some content is published")</label>
</div>
</fieldset>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="webpages:Enabled" value="false" />
</appSettings>
<system.web>
<httpHandlers>
</httpHandlers>
<!--
Enabling request validation in view pages would cause validation to occur
after the input has already been processed by the controller. By default
MVC performs request validation before a controller processes the input.
To change this behavior apply the ValidateInputAttribute to a
controller or action.
-->
<pages
validateRequest="false"
pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<controls>
<add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" namespace="System.Web.Mvc" tagPrefix="mvc" />
</controls>
</pages>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<handlers>
</handlers>
</system.webServer>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="2.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<remove name="host" />
<remove name="pages" />
<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
</sectionGroup>
</configSections>
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<pages pageBaseType="Orchard.Mvc.ViewEngines.Razor.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="System.Linq"/>
<add namespace="System.Collections.Generic"/>
<add namespace="Orchard.Mvc.Html"/>
</namespaces>
</pages>
</system.web.webPages.razor>
<system.web>
<compilation targetFramework="4.0">
<assemblies>
<add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</assemblies>
</compilation>
</system.web>
</configuration>

View File

@@ -18,6 +18,7 @@
<OldToolsVersion>3.5</OldToolsVersion>
<UpgradeBackupLocation />
<TargetFrameworkProfile />
<UseIISExpress>false</UseIISExpress>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -48,11 +49,68 @@
<ItemGroup>
<Content Include="Scripts\jquery-1.5.1.js" />
<Content Include="Scripts\jquery-1.5.1.min.js" />
<Content Include="Scripts\jquery.effects.blind.js" />
<Content Include="Scripts\jquery.effects.blind.min.js" />
<Content Include="Scripts\jquery.effects.bounce.js" />
<Content Include="Scripts\jquery.effects.bounce.min.js" />
<Content Include="Scripts\jquery.effects.clip.js" />
<Content Include="Scripts\jquery.effects.clip.min.js" />
<Content Include="Scripts\jquery.effects.core.js" />
<Content Include="Scripts\jquery.effects.core.min.js" />
<Content Include="Scripts\jquery.effects.drop.js" />
<Content Include="Scripts\jquery.effects.drop.min.js" />
<Content Include="Scripts\jquery.effects.explode.js" />
<Content Include="Scripts\jquery.effects.explode.min.js" />
<Content Include="Scripts\jquery.effects.fade.js" />
<Content Include="Scripts\jquery.effects.fade.min.js" />
<Content Include="Scripts\jquery.effects.fold.js" />
<Content Include="Scripts\jquery.effects.fold.min.js" />
<Content Include="Scripts\jquery.effects.highlight.js" />
<Content Include="Scripts\jquery.effects.highlight.min.js" />
<Content Include="Scripts\jquery.effects.pulsate.js" />
<Content Include="Scripts\jquery.effects.pulsate.min.js" />
<Content Include="Scripts\jquery.effects.scale.js" />
<Content Include="Scripts\jquery.effects.scale.min.js" />
<Content Include="Scripts\jquery.effects.shake.js" />
<Content Include="Scripts\jquery.effects.shake.min.js" />
<Content Include="Scripts\jquery.effects.slide.js" />
<Content Include="Scripts\jquery.effects.slide.min.js" />
<Content Include="Scripts\jquery.effects.transfer.js" />
<Content Include="Scripts\jquery.effects.transfer.min.js" />
<Content Include="Scripts\jquery.ui.accordion.js" />
<Content Include="Scripts\jquery.ui.accordion.min.js" />
<Content Include="Scripts\jquery.ui.autocomplete.js" />
<Content Include="Scripts\jquery.ui.autocomplete.min.js" />
<Content Include="Scripts\jquery.ui.button.js" />
<Content Include="Scripts\jquery.ui.button.min.js" />
<Content Include="Scripts\jquery.ui.core.js" />
<Content Include="Scripts\jquery.ui.core.min.js" />
<Content Include="Scripts\jquery.ui.datepicker.js" />
<Content Include="Scripts\jquery.ui.datepicker.min.js" />
<Content Include="Scripts\jquery.ui.dialog.js" />
<Content Include="Scripts\jquery.ui.dialog.min.js" />
<Content Include="Scripts\jquery.ui.draggable.js" />
<Content Include="Scripts\jquery.ui.draggable.min.js" />
<Content Include="Scripts\jquery.ui.droppable.js" />
<Content Include="Scripts\jquery.ui.droppable.min.js" />
<Content Include="Scripts\jquery.ui.mouse.js" />
<Content Include="Scripts\jquery.ui.mouse.min.js" />
<Content Include="Scripts\jquery.ui.position.js" />
<Content Include="Scripts\jquery.ui.position.min.js" />
<Content Include="Scripts\jquery.ui.progressbar.js" />
<Content Include="Scripts\jquery.ui.progressbar.min.js" />
<Content Include="Scripts\jquery.ui.resizable.js" />
<Content Include="Scripts\jquery.ui.resizable.min.js" />
<Content Include="Scripts\jquery.ui.selectable.js" />
<Content Include="Scripts\jquery.ui.selectable.min.js" />
<Content Include="Scripts\jquery.ui.slider.js" />
<Content Include="Scripts\jquery.ui.slider.min.js" />
<Content Include="Scripts\jquery.ui.sortable.js" />
<Content Include="Scripts\jquery.ui.sortable.min.js" />
<Content Include="Scripts\jquery.ui.tabs.js" />
<Content Include="Scripts\jquery.ui.tabs.min.js" />
<Content Include="Scripts\jquery.ui.widget.js" />
<Content Include="Scripts\jquery.ui.widget.min.js" />
<Content Include="Scripts\jquery.utils.js" />
<Content Include="Scripts\ui.timepickr.js" />
<Content Include="Styles\images\ui-bg_flat_0_aaaaaa_40x100.png" />

View File

@@ -18,6 +18,7 @@
<OldToolsVersion>3.5</OldToolsVersion>
<UpgradeBackupLocation />
<TargetFrameworkProfile />
<UseIISExpress>true</UseIISExpress>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -199,8 +200,7 @@
<AutoAssignPort>False</AutoAssignPort>
<DevelopmentServerPort>30320</DevelopmentServerPort>
<DevelopmentServerVPath>/OrchardLocal</DevelopmentServerVPath>
<IISUrl>
</IISUrl>
<IISUrl>http://localhost:30320/OrchardLocal</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>

View File

@@ -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}

View File

@@ -0,0 +1,8 @@
using System;
namespace Orchard.Environment.Warmup {
public class StartupResult {
public IOrchardHost Host { get; set; }
public Exception Error { get; set; }
}
}

View File

@@ -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));
}
}
}

View File

@@ -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

View File

@@ -183,6 +183,8 @@
<Compile Include="Environment\IAssemblyLoader.cs" />
<Compile Include="Environment\HostComponentsConfigModule.cs" />
<Compile Include="Environment\ViewsBackgroundCompilation.cs" />
<Compile Include="Environment\Warmup\StartupResult.cs" />
<Compile Include="Environment\Warmup\WarmupUtility.cs" />
<Compile Include="Environment\WorkContextImplementation.cs" />
<Compile Include="Environment\WorkContextModule.cs" />
<Compile Include="Environment\WorkContextProperty.cs" />

View File

@@ -13,5 +13,6 @@ namespace Orchard.Settings {
string SiteCulture { get; set; }
ResourceDebugMode ResourceDebugMode { get; set; }
int PageSize { get; set; }
string BaseUrl { get; }
}
}