Updating Warmup UI

--HG--
branch : 1.x
This commit is contained in:
Sebastien Ros
2011-06-03 17:44:46 -07:00
parent 2dda95fbf4
commit a605420d11
16 changed files with 416 additions and 143 deletions

View File

@@ -6,6 +6,7 @@ using System.Xml;
using Autofac; using Autofac;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using Orchard.Environment.Configuration;
using Orchard.Environment.Warmup; using Orchard.Environment.Warmup;
using Orchard.FileSystems.AppData; using Orchard.FileSystems.AppData;
using Orchard.FileSystems.LockFile; using Orchard.FileSystems.LockFile;
@@ -26,11 +27,13 @@ namespace Orchard.Tests.Modules.Warmup {
private Mock<IWebDownloader> _webDownloader; private Mock<IWebDownloader> _webDownloader;
private IOrchardServices _orchardServices; private IOrchardServices _orchardServices;
private WarmupSettingsPart _settings; private WarmupSettingsPart _settings;
private IWarmupReportManager _reportManager;
private readonly string _basePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); private readonly string _basePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
private string _warmupFilename, _lockFilename; private string _warmupFilename, _lockFilename;
private const string WarmupFolder = "Warmup"; private const string WarmupFolder = "Warmup";
private const string TenantFolder = "Sites/Default";
[TestFixtureTearDown] [TestFixtureTearDown]
public void Clean() { public void Clean() {
@@ -60,15 +63,18 @@ namespace Orchard.Tests.Modules.Warmup {
builder.RegisterType<DefaultLockFileManager>().As<ILockFileManager>(); builder.RegisterType<DefaultLockFileManager>().As<ILockFileManager>();
builder.RegisterType<WarmupUpdater>().As<IWarmupUpdater>(); builder.RegisterType<WarmupUpdater>().As<IWarmupUpdater>();
builder.RegisterType<StubClock>().As<IClock>(); builder.RegisterType<StubClock>().As<IClock>();
builder.RegisterType<WarmupReportManager>().As<IWarmupReportManager>();
builder.RegisterInstance(new ShellSettings { Name = "Default" }).As<ShellSettings>();
builder.RegisterInstance(_clock = new StubClock()).As<IClock>(); builder.RegisterInstance(_clock = new StubClock()).As<IClock>();
builder.RegisterInstance(_webDownloader.Object).As<IWebDownloader>(); builder.RegisterInstance(_webDownloader.Object).As<IWebDownloader>();
_container = builder.Build(); _container = builder.Build();
_lockFileManager = _container.Resolve<ILockFileManager>(); _lockFileManager = _container.Resolve<ILockFileManager>();
_warmupUpdater = _container.Resolve<IWarmupUpdater>(); _warmupUpdater = _container.Resolve<IWarmupUpdater>();
_reportManager = _container.Resolve<IWarmupReportManager>();
_warmupFilename = _appDataFolder.Combine(WarmupFolder, "warmup.txt"); _warmupFilename = _appDataFolder.Combine(TenantFolder, "warmup.txt");
_lockFilename = _appDataFolder.Combine(WarmupFolder, "warmup.txt.lock"); _lockFilename = _appDataFolder.Combine(TenantFolder, "warmup.txt.lock");
} }
[Test] [Test]
@@ -91,8 +97,7 @@ namespace Orchard.Tests.Modules.Warmup {
_lockFileManager.TryAcquireLock(_lockFilename, ref lockFile); _lockFileManager.TryAcquireLock(_lockFilename, ref lockFile);
using(lockFile) { using(lockFile) {
_warmupUpdater.Generate(); _warmupUpdater.Generate();
// warmup file + lock file Assert.That(_appDataFolder.ListFiles(WarmupFolder).Count(), Is.EqualTo(0));
Assert.That(_appDataFolder.ListFiles(WarmupFolder).Count(), Is.EqualTo(2));
} }
_warmupUpdater.Generate(); _warmupUpdater.Generate();
@@ -114,11 +119,14 @@ namespace Orchard.Tests.Modules.Warmup {
_warmupUpdater.Generate(); _warmupUpdater.Generate();
var files = _appDataFolder.ListFiles(WarmupFolder).ToList(); 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"))));
Assert.That(files, Has.Some.Matches<string>(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About")))); Assert.That(files, Has.Some.Matches<string>(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About"))));
files = _appDataFolder.ListFiles(TenantFolder).ToList();
Assert.That(files, Has.Some.Matches<string>(x => x == _appDataFolder.Combine(TenantFolder, "warmup.txt")));
Assert.That(files, Has.Some.Matches<string>(x => x == _appDataFolder.Combine(TenantFolder, "warmup.xml")));
var homepageContent = _appDataFolder.ReadFile(_appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net"))); 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 aboutcontent = _appDataFolder.ReadFile(_appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About")));
@@ -141,8 +149,6 @@ namespace Orchard.Tests.Modules.Warmup {
_warmupUpdater.Generate(); _warmupUpdater.Generate();
var files = _appDataFolder.ListFiles(WarmupFolder).ToList(); 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.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")))); Assert.That(files, Has.None.Matches<string>(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net/About"))));
} }
@@ -158,9 +164,7 @@ namespace Orchard.Tests.Modules.Warmup {
_warmupUpdater.Generate(); _warmupUpdater.Generate();
var files = _appDataFolder.ListFiles(WarmupFolder).ToList(); var files = _appDataFolder.ListFiles(WarmupFolder).ToList();
// warmup + content file Assert.That(files.Count, Is.EqualTo(1));
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")))); Assert.That(files, Has.Some.Matches<string>(x => x == _appDataFolder.Combine(WarmupFolder, WarmupUtility.EncodeUrl("http://orchardproject.net"))));
} }
@@ -258,8 +262,6 @@ namespace Orchard.Tests.Modules.Warmup {
_warmupUpdater.Generate(); _warmupUpdater.Generate();
var files = _appDataFolder.ListFiles(WarmupFolder).ToList(); 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"))));
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://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"))));
@@ -277,5 +279,95 @@ namespace Orchard.Tests.Modules.Warmup {
Assert.That(wwwaboutcontent, Is.EqualTo("Bar")); Assert.That(wwwaboutcontent, Is.EqualTo("Bar"));
} }
[Test]
public void ReportIsCreated() {
_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();
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 report = _reportManager.Read().ToList();
Assert.That(report.Count(), Is.EqualTo(2));
Assert.That(report, Has.Some.Matches<ReportEntry>(x => x.RelativeUrl == "/"));
Assert.That(report, Has.Some.Matches<ReportEntry>(x => x.RelativeUrl == "/About"));
}
[Test]
public void ShouldNotDeleteOtherFiles() {
_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 });
// Create a static file in the warmup folder
_appDataFolder.CreateFile(_appDataFolder.Combine(WarmupFolder, "foo.txt"), "Foo");
_warmupUpdater.Generate();
var files = _appDataFolder.ListFiles(WarmupFolder).ToList();
Assert.That(files, Has.Some.Matches<string>(x => x == _appDataFolder.Combine(WarmupFolder, "foo.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"))));
}
[Test]
public void ClearingUrlsShouldDeleteContent() {
_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();
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"))));
_settings.Urls = @"";
_warmupUpdater.Generate();
files = _appDataFolder.ListFiles(WarmupFolder).ToList();
Assert.That(files.Count, Is.EqualTo(0));
}
} }
} }

View File

@@ -14,6 +14,12 @@
@Html.EditorFor(m => m.SiteName) @Html.EditorFor(m => m.SiteName)
@Html.ValidationMessage("SiteName", "*") @Html.ValidationMessage("SiteName", "*")
</div> </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>
<div> <div>
<label for="SiteCulture">@T("Default Site Culture")</label> <label for="SiteCulture">@T("Default Site Culture")</label>
@Html.DropDownList("SiteCulture", new SelectList(Model.SiteCultures, Model.SiteCulture)) @Html.DropDownList("SiteCulture", new SelectList(Model.SiteCultures, Model.SiteCulture))
@@ -41,10 +47,4 @@
@Html.TextBoxFor(m => m.PageSize, new { @class = "text-small" }) @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> <span class="hint">@T("Determines the default number of items that are shown per page.")</span>
</div> </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> </fieldset>

View File

@@ -10,8 +10,9 @@ namespace Orchard.Warmup {
public void GetNavigation(NavigationBuilder builder) { public void GetNavigation(NavigationBuilder builder) {
builder builder
.Add(T("Settings"), menu => menu .Add(T("Settings"), menu => menu
.Add(T("Warmup" ), "10.0", item => item.Action("Index", "Admin", new { area = "Orchard.Warmup" }).Permission(StandardPermissions.SiteOwner)) .Add(T("Performance"), "10.0", subMenu => subMenu.Action("Index", "Admin", new { area = "Orchard.Warmup" }).Permission(StandardPermissions.SiteOwner)
); .Add(T("Warmup"), "10.0", item => item.Action("Index", "Admin", new { area = "Orchard.Warmup" }).Permission(StandardPermissions.SiteOwner).LocalNav())
));
} }
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

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

@@ -1,25 +1,28 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Web.Mvc; using System.Web.Mvc;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.Core.Contents.Controllers; using Orchard.Core.Contents.Controllers;
using Orchard.FileSystems.AppData;
using Orchard.Localization; using Orchard.Localization;
using Orchard.Security; using Orchard.Security;
using Orchard.Warmup.Models; using Orchard.Warmup.Models;
using Orchard.UI.Notify; using Orchard.UI.Notify;
using Orchard.Warmup.Services; using Orchard.Warmup.Services;
using Orchard.Warmup.ViewModels;
namespace Orchard.Warmup.Controllers { namespace Orchard.Warmup.Controllers {
[ValidateInput(false)] [ValidateInput(false)]
public class AdminController : Controller, IUpdateModel { public class AdminController : Controller, IUpdateModel {
private readonly IWarmupScheduler _warmupScheduler; private readonly IWarmupUpdater _warmupUpdater;
private readonly IWarmupReportManager _reportManager;
public AdminController( public AdminController(
IOrchardServices services, IOrchardServices services,
IWarmupScheduler warmupScheduler, IWarmupUpdater warmupUpdater,
IAppDataFolder appDataFolder) { IWarmupReportManager reportManager) {
_warmupScheduler = warmupScheduler; _warmupUpdater = warmupUpdater;
_reportManager = reportManager;
Services = services; Services = services;
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
@@ -33,7 +36,13 @@ namespace Orchard.Warmup.Controllers {
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
var warmupPart = Services.WorkContext.CurrentSite.As<WarmupSettingsPart>(); var warmupPart = Services.WorkContext.CurrentSite.As<WarmupSettingsPart>();
return View(warmupPart);
var viewModel = new WarmupViewModel {
Settings = warmupPart,
ReportEntries = _reportManager.Read()
};
return View(viewModel);
} }
[FormValueRequired("submit")] [FormValueRequired("submit")]
@@ -42,11 +51,14 @@ namespace Orchard.Warmup.Controllers {
if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to manage settings"))) if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to manage settings")))
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
var warmupPart = Services.WorkContext.CurrentSite.As<WarmupSettingsPart>(); var viewModel = new WarmupViewModel {
Settings = Services.WorkContext.CurrentSite.As<WarmupSettingsPart>(),
ReportEntries = Enumerable.Empty<ReportEntry>()
};
if(TryUpdateModel(warmupPart)) { if (TryUpdateModel(viewModel)) {
if (!String.IsNullOrEmpty(warmupPart.Urls)) { if (!String.IsNullOrEmpty(viewModel.Settings.Urls)) {
using (var urlReader = new StringReader(warmupPart.Urls)) { using (var urlReader = new StringReader(viewModel.Settings.Urls)) {
string relativeUrl; string relativeUrl;
while (null != (relativeUrl = urlReader.ReadLine())) { while (null != (relativeUrl = urlReader.ReadLine())) {
if (!Uri.IsWellFormedUriString(relativeUrl, UriKind.Relative) || !(relativeUrl.StartsWith("/"))) { if (!Uri.IsWellFormedUriString(relativeUrl, UriKind.Relative) || !(relativeUrl.StartsWith("/"))) {
@@ -57,8 +69,8 @@ namespace Orchard.Warmup.Controllers {
} }
} }
if (warmupPart.Scheduled) { if (viewModel.Settings.Scheduled) {
if (warmupPart.Delay <= 0) { if (viewModel.Settings.Delay <= 0) {
AddModelError("Delay", T("Delay must be greater than zero.")); AddModelError("Delay", T("Delay must be greater than zero."));
} }
} }
@@ -66,21 +78,11 @@ namespace Orchard.Warmup.Controllers {
if (ModelState.IsValid) { if (ModelState.IsValid) {
Services.Notifier.Information(T("Warmup updated successfully.")); Services.Notifier.Information(T("Warmup updated successfully."));
} }
return View(warmupPart);
}
[FormValueRequired("submit.Generate")]
[HttpPost, ActionName("Index")]
public ActionResult IndexPostGenerate() {
var result = IndexPost();
if (ModelState.IsValid) { if (ModelState.IsValid) {
_warmupScheduler.Schedule(true); _warmupUpdater.Generate();
Services.Notifier.Information(T("Static pages are currently being generated."));
} }
return result; return RedirectToAction("Index");
} }
bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) { bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) {

View File

@@ -0,0 +1,10 @@
using System;
namespace Orchard.Warmup.Models {
public class ReportEntry {
public string RelativeUrl { get; set; }
public string Filename { get; set; }
public int StatusCode { get; set; }
public DateTime CreatedUtc { get; set; }
}
}

View File

@@ -59,6 +59,9 @@
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Content\Admin\images\offline.gif" />
<Content Include="Content\Admin\images\online.gif" />
<Content Include="Styles\orchard-warmup-admin.css" />
<Content Include="web.config" /> <Content Include="web.config" />
<Content Include="Views\Web.config" /> <Content Include="Views\Web.config" />
<Content Include="Scripts\Web.config" /> <Content Include="Scripts\Web.config" />
@@ -83,8 +86,11 @@
<Compile Include="Handlers\WarmupContentHandler.cs" /> <Compile Include="Handlers\WarmupContentHandler.cs" />
<Compile Include="Handlers\WarmupSettingsPartHandler.cs" /> <Compile Include="Handlers\WarmupSettingsPartHandler.cs" />
<Compile Include="Migrations.cs" /> <Compile Include="Migrations.cs" />
<Compile Include="Models\ReportEntry.cs" />
<Compile Include="Models\WarmupSettingsPart.cs" /> <Compile Include="Models\WarmupSettingsPart.cs" />
<Compile Include="Models\WarmupSettingsPartRecord.cs" /> <Compile Include="Models\WarmupSettingsPartRecord.cs" />
<Compile Include="Services\WarmupReportManager.cs" />
<Compile Include="Services\IWarmupReportManager.cs" />
<Compile Include="Services\IWarmupScheduler.cs" /> <Compile Include="Services\IWarmupScheduler.cs" />
<Compile Include="Services\WarmupScheduler.cs" /> <Compile Include="Services\WarmupScheduler.cs" />
<Compile Include="Services\WebDownloader.cs" /> <Compile Include="Services\WebDownloader.cs" />
@@ -93,10 +99,12 @@
<Compile Include="Services\SettingsBanner.cs" /> <Compile Include="Services\SettingsBanner.cs" />
<Compile Include="Services\WarmupTask.cs" /> <Compile Include="Services\WarmupTask.cs" />
<Compile Include="Services\WarmupUpdater.cs" /> <Compile Include="Services\WarmupUpdater.cs" />
<Compile Include="ViewModels\WarmupViewModel.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup />
<ItemGroup> <ItemGroup>
<Content Include="Views\EditorTemplates\Parts.Warmup.SiteSettings.cshtml" /> <Content Include="Content\Web.config">
<SubType>Designer</SubType>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Placement.info" /> <Content Include="Placement.info" />

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
using Orchard.Warmup.Models;
namespace Orchard.Warmup.Services {
public interface IWarmupReportManager : IDependency {
IEnumerable<ReportEntry> Read();
void Create(IEnumerable<ReportEntry> reportEntries);
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Linq;
using Orchard.Environment.Configuration;
using Orchard.FileSystems.AppData;
using Orchard.Warmup.Models;
namespace Orchard.Warmup.Services {
public class WarmupReportManager : IWarmupReportManager {
private readonly IAppDataFolder _appDataFolder;
private const string WarmupReportFilename = "warmup.xml";
private readonly string _warmupReportPath;
public WarmupReportManager(
ShellSettings shellSettings,
IAppDataFolder appDataFolder) {
_appDataFolder = appDataFolder;
_warmupReportPath = _appDataFolder.Combine("Sites", _appDataFolder.Combine(shellSettings.Name, WarmupReportFilename));
}
public IEnumerable<ReportEntry> Read() {
if(!_appDataFolder.FileExists(_warmupReportPath)) {
yield break;
}
var warmupReportContent = _appDataFolder.ReadFile(_warmupReportPath);
var doc = XDocument.Parse(warmupReportContent);
foreach (var entryNode in doc.Root.Descendants("ReportEntry")) {
yield return new ReportEntry {
CreatedUtc = XmlConvert.ToDateTime(entryNode.Attribute("CreatedUtc").Value, XmlDateTimeSerializationMode.Utc),
Filename = entryNode.Attribute("Filename").Value,
RelativeUrl = entryNode.Attribute("RelativeUrl").Value,
StatusCode = Int32.Parse(entryNode.Attribute("StatusCode").Value)
};
}
}
public void Create(IEnumerable<ReportEntry> reportEntries) {
var report = new XDocument(new XElement("WarmupReport"));
foreach (var reportEntry in reportEntries) {
report.Root.Add(
new XElement("ReportEntry",
new XAttribute("RelativeUrl", reportEntry.RelativeUrl),
new XAttribute("Filename", reportEntry.Filename),
new XAttribute("StatusCode", reportEntry.StatusCode),
new XAttribute("CreatedUtc", XmlConvert.ToString(reportEntry.CreatedUtc, XmlDateTimeSerializationMode.Utc))
)
);
}
_appDataFolder.CreateFile(_warmupReportPath, report.ToString());
}
}
}

View File

@@ -1,9 +1,11 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Web; using System.Web;
using System.Xml; using System.Xml;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.Environment.Configuration;
using Orchard.Environment.Warmup; using Orchard.Environment.Warmup;
using Orchard.FileSystems.AppData; using Orchard.FileSystems.AppData;
using Orchard.FileSystems.LockFile; using Orchard.FileSystems.LockFile;
@@ -18,8 +20,11 @@ namespace Orchard.Warmup.Services {
private readonly IClock _clock; private readonly IClock _clock;
private readonly IAppDataFolder _appDataFolder; private readonly IAppDataFolder _appDataFolder;
private readonly IWebDownloader _webDownloader; private readonly IWebDownloader _webDownloader;
private readonly IWarmupReportManager _reportManager;
private const string BaseFolder = "Warmup"; private const string BaseFolder = "Warmup";
private const string WarmupFilename = "warmup.txt"; private const string WarmupFilename = "warmup.txt";
private readonly string _warmupPath;
private readonly string _lockFilename; private readonly string _lockFilename;
public WarmupUpdater( public WarmupUpdater(
@@ -27,13 +32,18 @@ namespace Orchard.Warmup.Services {
ILockFileManager lockFileManager, ILockFileManager lockFileManager,
IClock clock, IClock clock,
IAppDataFolder appDataFolder, IAppDataFolder appDataFolder,
IWebDownloader webDownloader) { IWebDownloader webDownloader,
IWarmupReportManager reportManager,
ShellSettings shellSettings) {
_orchardServices = orchardServices; _orchardServices = orchardServices;
_lockFileManager = lockFileManager; _lockFileManager = lockFileManager;
_clock = clock; _clock = clock;
_appDataFolder = appDataFolder; _appDataFolder = appDataFolder;
_webDownloader = webDownloader; _webDownloader = webDownloader;
_lockFilename = _appDataFolder.Combine(BaseFolder, WarmupFilename + ".lock"); _reportManager = reportManager;
_lockFilename = _appDataFolder.Combine("Sites", _appDataFolder.Combine(shellSettings.Name, WarmupFilename + ".lock"));
_warmupPath = _appDataFolder.Combine("Sites", _appDataFolder.Combine(shellSettings.Name, WarmupFilename));
Logger = NullLogger.Instance; Logger = NullLogger.Instance;
} }
@@ -44,8 +54,8 @@ namespace Orchard.Warmup.Services {
var baseUrl = _orchardServices.WorkContext.CurrentSite.BaseUrl; var baseUrl = _orchardServices.WorkContext.CurrentSite.BaseUrl;
var part = _orchardServices.WorkContext.CurrentSite.As<WarmupSettingsPart>(); var part = _orchardServices.WorkContext.CurrentSite.As<WarmupSettingsPart>();
// do nothing while the base url setting is not defined, or if there is no page defined // do nothing while the base url setting is not defined
if (String.IsNullOrWhiteSpace(baseUrl) || String.IsNullOrWhiteSpace(part.Urls)) { if (String.IsNullOrWhiteSpace(baseUrl)) {
return; return;
} }
@@ -60,10 +70,9 @@ namespace Orchard.Warmup.Services {
// check if we need to regenerate the pages by reading the last time it has been done // 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 // 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 // 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)) {
if(_appDataFolder.FileExists(warmupPath)) {
try { try {
var warmupContent = _appDataFolder.ReadFile(warmupPath); var warmupContent = _appDataFolder.ReadFile(_warmupPath);
var expired = XmlConvert.ToDateTimeOffset(warmupContent).AddMinutes(part.Delay); var expired = XmlConvert.ToDateTimeOffset(warmupContent).AddMinutes(part.Delay);
if (expired > _clock.UtcNow) { if (expired > _clock.UtcNow) {
return; return;
@@ -71,28 +80,39 @@ namespace Orchard.Warmup.Services {
} }
catch { catch {
// invalid file, delete continue processing // invalid file, delete continue processing
_appDataFolder.DeleteFile(warmupPath); _appDataFolder.DeleteFile(_warmupPath);
} }
} }
// delete existing static page files // delete peviously generated pages, by reading the Warmup Report file
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 { try {
_appDataFolder.DeleteFile(filename); var encodedPrefix = WarmupUtility.EncodeUrl("http://www.");
foreach (var reportEntry in _reportManager.Read()) {
try {
// use FileName as the SiteBaseUrl could have changed in the meantime
var path = _appDataFolder.Combine(BaseFolder, reportEntry.Filename);
_appDataFolder.DeleteFile(path);
// delete the www-less version too if it's available
if (reportEntry.Filename.StartsWith(encodedPrefix, StringComparison.OrdinalIgnoreCase)) {
var filename = WarmupUtility.EncodeUrl("http://") + reportEntry.Filename.Substring(encodedPrefix.Length);
path = _appDataFolder.Combine(BaseFolder, filename);
_appDataFolder.DeleteFile(path);
}
} }
catch (Exception e) { catch (Exception e) {
// ignore files which could not be deleted Logger.Error(e, "Could not delete specific warmup file: ", reportEntry.Filename);
Logger.Error(e, "Could not delete file {0}", filename);
} }
} }
}
catch(Exception e) {
Logger.Error(e, "Could not read warmup report file");
}
var reportEntries = new List<ReportEntry>();
if (!String.IsNullOrEmpty(part.Urls)) {
// loop over every relative url to generate the contents // loop over every relative url to generate the contents
using (var urlReader = new StringReader(part.Urls)) { using (var urlReader = new StringReader(part.Urls)) {
string relativeUrl; string relativeUrl;
@@ -102,14 +122,24 @@ namespace Orchard.Warmup.Services {
try { try {
url = VirtualPathUtility.RemoveTrailingSlash(baseUrl) + relativeUrl; url = VirtualPathUtility.RemoveTrailingSlash(baseUrl) + relativeUrl;
var download = _webDownloader.Download(url);
if (download != null && download.StatusCode == HttpStatusCode.OK) {
var filename = WarmupUtility.EncodeUrl(url.TrimEnd('/')); var filename = WarmupUtility.EncodeUrl(url.TrimEnd('/'));
var path = _appDataFolder.Combine(BaseFolder, filename); var path = _appDataFolder.Combine(BaseFolder, filename);
var download = _webDownloader.Download(url);
if (download != null) {
if (download.StatusCode == HttpStatusCode.OK) {
// success
_appDataFolder.CreateFile(path, download.Content); _appDataFolder.CreateFile(path, download.Content);
// if the base url contains http://www, then also render the www-less one reportEntries.Add(new ReportEntry {
RelativeUrl = relativeUrl,
Filename = filename,
StatusCode = (int) download.StatusCode,
CreatedUtc = _clock.UtcNow
});
// if the base url contains http://www, then also render the www-less one);
if (url.StartsWith("http://www.", StringComparison.OrdinalIgnoreCase)) { if (url.StartsWith("http://www.", StringComparison.OrdinalIgnoreCase)) {
url = "http://" + url.Substring("http://www.".Length); url = "http://" + url.Substring("http://www.".Length);
@@ -117,7 +147,24 @@ namespace Orchard.Warmup.Services {
path = _appDataFolder.Combine(BaseFolder, filename); path = _appDataFolder.Combine(BaseFolder, filename);
_appDataFolder.CreateFile(path, download.Content); _appDataFolder.CreateFile(path, download.Content);
} }
}
else {
reportEntries.Add(new ReportEntry {
RelativeUrl = relativeUrl,
Filename = filename,
StatusCode = (int) download.StatusCode,
CreatedUtc = _clock.UtcNow
});
}
}
else {
// download failed
reportEntries.Add(new ReportEntry {
RelativeUrl = relativeUrl,
Filename = filename,
StatusCode = 0,
CreatedUtc = _clock.UtcNow
});
} }
} }
catch (Exception e) { catch (Exception e) {
@@ -125,9 +172,12 @@ namespace Orchard.Warmup.Services {
} }
} }
} }
}
_reportManager.Create(reportEntries);
// finally write the time the generation has been executed // finally write the time the generation has been executed
_appDataFolder.CreateFile(warmupPath, XmlConvert.ToString(_clock.UtcNow, XmlDateTimeSerializationMode.Utc)); _appDataFolder.CreateFile(_warmupPath, XmlConvert.ToString(_clock.UtcNow, XmlDateTimeSerializationMode.Utc));
} }
} }
@@ -139,9 +189,8 @@ namespace Orchard.Warmup.Services {
} }
using (lockFile) { using (lockFile) {
var warmupPath = _appDataFolder.Combine(BaseFolder, WarmupFilename); if (_appDataFolder.FileExists(_warmupPath)) {
if (_appDataFolder.FileExists(warmupPath)) { _appDataFolder.DeleteFile(_warmupPath);
_appDataFolder.DeleteFile(warmupPath);
} }
} }

View File

@@ -0,0 +1,8 @@

td.status-error {
background: transparent url(../Content/Admin/images/offline.gif) no-repeat right;
}
td.status-ok {
background: transparent url(../Content/Admin/images/online.gif) no-repeat right;
}

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
using Orchard.Warmup.Models;
namespace Orchard.Warmup.ViewModels {
public class WarmupViewModel {
public WarmupSettingsPart Settings { get; set; }
public IEnumerable<ReportEntry> ReportEntries { get; set; }
}
}

View File

@@ -1,38 +1,72 @@
@model Orchard.Warmup.Models.WarmupSettingsPart @model Orchard.Warmup.ViewModels.WarmupViewModel
@using Orchard.Utility.Extensions; @using Orchard.Utility.Extensions;
@using Orchard.Warmup.Models; @using Orchard.Warmup.Models;
@{ Layout.Title = T("Settings").ToString(); } @{
Style.Include("orchard-warmup-admin.css");
Layout.Title = T("Performance").ToString();
}
<p>The urls below will be requested using @Html.Link(WorkContext.CurrentSite.BaseUrl, WorkContext.CurrentSite.BaseUrl) as a base url. You can change it on the @Html.ActionLink(T("General Settings page").Text, "Index", new { controller = "Admin", area = "Settings" }).</p>
@using (Html.BeginFormAntiForgeryPost()) { @using (Html.BeginFormAntiForgeryPost()) {
@Html.ValidationSummary() @Html.ValidationSummary()
<fieldset> <fieldset>
<legend>@T("Warmup")</legend>
<div> <div>
<label for="@Html.FieldIdFor(m => m.Urls)">@T("Urls for which static warmup pages will be generated")</label> @Html.TextAreaFor(m => m.Settings.Urls, new { @class = "textMedium" })
@Html.TextAreaFor(m => m.Urls, new { @class = "textMedium" })
<span class="hint">@T("This must be a set of relative paths, e.g., /, /About")</span> <span class="hint">@T("This must be a set of relative paths, e.g., /, /About")</span>
</div> </div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<div> <div>
@Html.EditorFor(m => m.Scheduled) @Html.EditorFor(m => m.Settings.Scheduled)
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.Scheduled)">@T("Generate warmup pages periodically")</label> <label class="forcheckbox" for="@Html.FieldIdFor(m => m.Settings.Scheduled)">@T("Generate warmup pages periodically")</label>
</div> </div>
<div data-controllerid="@Html.FieldIdFor(m => m.Scheduled)"> <div data-controllerid="@Html.FieldIdFor(m => m.Settings.Scheduled)">
@T("Every") @T("Every")
@Html.TextBoxFor(m => m.Delay, new { @class = "text-small" }) @Html.TextBoxFor(m => m.Settings.Delay, new { @class = "text-small" })
@T("minutes") @T("minutes")
@Html.ValidationMessage("Delay", "*") @Html.ValidationMessage("Delay", "*")
</div> </div>
<div> <div>
@Html.EditorFor(m => m.OnPublish) @Html.EditorFor(m => m.Settings.OnPublish)
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.OnPublish)">@T("Generate warmup pages each time some content is published")</label> <label class="forcheckbox" for="@Html.FieldIdFor(m => m.Settings.OnPublish)">@T("Generate warmup pages any time some content is published")</label>
</div> </div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<button class="primaryAction" name="submit" value="@T("Save")" type="submit">@T("Save")</button> <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> </fieldset>
} }
<fieldset>
<label>@T("Report")</label>
<table class="items" summary="@T("This is a table of the reports in your application")">
<colgroup>
<col id="Col1" />
<col id="Col2" />
<col id="Col3" />
<col id="Col4" />
</colgroup>
<thead>
<tr>
<th scope="col">@T("Url")</th>
<th scope="col">@T("Status")</th>
<th scope="col">@T("Date")</th>
<th scope="col"></th>
</tr>
</thead>
@foreach (var reportEntry in Model.ReportEntries) {
<tr>
<td class="status-@(reportEntry.StatusCode != 200 ? "error" : "ok")" >
@Html.Link(Html.Encode(reportEntry.RelativeUrl), reportEntry.RelativeUrl, new { target = "_blank" })
</td>
<td>
@reportEntry.StatusCode
</td>
<td>
@Display.DateTimeRelative(dateTimeUtc: reportEntry.CreatedUtc).ToString()
</td>
</tr>
}
</table>
</fieldset>

View File

@@ -1,28 +0,0 @@
@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>