Adding a file system backed output cache provider

This commit is contained in:
Sebastien Ros
2015-08-05 14:21:49 -07:00
parent e346170f24
commit 41bd9ce9bc
4 changed files with 230 additions and 0 deletions

View File

@@ -7,9 +7,16 @@ OrchardVersion: 1.9
Description: Adds output caching functionality
Features:
Orchard.OutputCache:
Name: Output Cache
Description: Adds output caching functionality.
Category: Performance
Orchard.OutputCache.Database:
Name: Database Output Cache
Description: Activates a provider that stores output cache data in the database.
Category: Performance
Dependencies: Orchard.OutputCache
Orchard.OutputCache.FileSystem:
Name: File System Output Cache
Description: Activates a provider that stores output cache data in the App_Data folder.
Category: Performance
Dependencies: Orchard.OutputCache

View File

@@ -107,7 +107,9 @@
<Compile Include="Models\CacheSettingsPart.cs" />
<Compile Include="Models\CacheParameterRecord.cs" />
<Compile Include="Services\CacheService.cs" />
<Compile Include="Services\FileSystemOutputCacheBackgroundTask.cs" />
<Compile Include="Services\DatabaseOutputCacheBackgroundTask.cs" />
<Compile Include="Services\FileSystemOutputCacheProvider.cs" />
<Compile Include="Services\DatabaseOutputCacheProvider.cs" />
<Compile Include="Services\DefaultCacheControlStrategy.cs" />
<Compile Include="Services\DefaultTagCache.cs" />

View File

@@ -0,0 +1,56 @@
using System.Linq;
using Orchard.Caching;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions;
using Orchard.FileSystems.AppData;
using Orchard.Services;
using Orchard.Tasks;
namespace Orchard.OutputCache.Services {
[OrchardFeature("Orchard.OutputCache.FileSystem")]
/// <summary>
/// A background task deleting all App_Data output cache content.
/// </summary>
public class FileSystemOutputCacheBackgroundTask : IBackgroundTask {
private readonly IAppDataFolder _appDataFolder;
private readonly ShellSettings _shellSettings;
private readonly ICacheManager _cacheManager;
private readonly IClock _clock;
private readonly ISignals _signals;
private string _root;
public FileSystemOutputCacheBackgroundTask(
IAppDataFolder appDataFolder,
ShellSettings shellSettings,
ICacheManager cacheManager,
IClock clock,
ISignals signals) {
_appDataFolder = appDataFolder;
_shellSettings = shellSettings;
_cacheManager = cacheManager;
_clock = clock;
_signals = signals;
_root = _appDataFolder.Combine("OutputCache", _shellSettings.Name);
}
public void Sweep() {
foreach(var filename in _appDataFolder.ListFiles(_root).ToArray()) {
var validUntilUtc = _cacheManager.Get(filename, context => {
_signals.When(filename);
using (var stream = _appDataFolder.OpenFile(filename)) {
var cacheItem = FileSystemOutputCacheStorageProvider.Deserialize(stream);
return cacheItem.ValidUntilUtc;
}
});
if (_clock.UtcNow > validUntilUtc) {
_appDataFolder.DeleteFile(filename);
_signals.Trigger(filename);
}
}
}
}
}

View File

@@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.OutputCache.Models;
using Orchard.Environment.Extensions;
using Orchard.Logging;
using Orchard.Services;
using Orchard.FileSystems.AppData;
using Orchard.Environment.Configuration;
using System.Web;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace Orchard.OutputCache.Services {
[OrchardFeature("Orchard.OutputCache.FileSystem")]
[OrchardSuppressDependency("Orchard.OutputCache.Services.DefaultCacheStorageProvider")]
/// <summary>
/// This class provides an implementation of <see cref="IOutputCacheStorageProvider"/>
/// based on the local App_Data folder, inside <c>OuputCache/{tenant}</c>. It is not
/// recommended when used in a server farm.
/// The <see cref="CacheItem"/> instances are binary serialized.
/// </summary>
/// <remarks>
/// This provider doesn't implement quotas support yet.
/// </remarks>
public class FileSystemOutputCacheStorageProvider : IOutputCacheStorageProvider {
private readonly IClock _clock;
private readonly IAppDataFolder _appDataFolder;
private readonly ShellSettings _shellSettings;
private readonly string _root;
public FileSystemOutputCacheStorageProvider(IClock clock, IAppDataFolder appDataFolder, ShellSettings shellSettings) {
_appDataFolder = appDataFolder;
_clock = clock;
_shellSettings = shellSettings;
_root = _appDataFolder.Combine("OutputCache", _shellSettings.Name);
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public void Set(string key, CacheItem cacheItem) {
Retry(() => {
if (cacheItem == null) {
throw new ArgumentNullException("cacheItem");
}
if (cacheItem.ValidFor <= 0) {
return;
}
var filename = GetCacheItemFilename(key);
using (var stream = Serialize(cacheItem)) {
using (var fileStream = _appDataFolder.CreateFile(filename)) {
stream.CopyTo(fileStream);
}
}
});
}
public void Remove(string key) {
Retry(() => {
var filename = GetCacheItemFilename(key);
if (_appDataFolder.FileExists(filename)) {
_appDataFolder.DeleteFile(filename);
}
});
}
public void RemoveAll() {
foreach(var filename in _appDataFolder.ListFiles(_root)) {
if(_appDataFolder.FileExists(filename)) {
_appDataFolder.DeleteFile(filename);
}
}
}
public CacheItem GetCacheItem(string key) {
return Retry(() => {
var filename = GetCacheItemFilename(key);
if (!_appDataFolder.FileExists(filename)) {
return null;
}
using (var stream = _appDataFolder.OpenFile(filename)) {
if (stream == null) {
return null;
}
return Deserialize(stream);
}
});
}
public IEnumerable<CacheItem> GetCacheItems(int skip, int count) {
return _appDataFolder.ListFiles(_root)
.OrderBy(x => x)
.Skip(skip)
.Take(count)
.Select(filename => {
using (var stream = _appDataFolder.OpenFile(filename)) {
return Deserialize(stream);
}
})
.ToList();
}
public int GetCacheItemsCount() {
return _appDataFolder.ListFiles(_root).Count();
}
private string GetCacheItemFilename(string key) {
return _appDataFolder.Combine(_root, HttpUtility.UrlEncode(key));
}
internal static MemoryStream Serialize(CacheItem item) {
BinaryFormatter binaryFormatter = new BinaryFormatter();
var memoryStream = new MemoryStream();
binaryFormatter.Serialize(memoryStream, item);
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
internal static CacheItem Deserialize(Stream stream) {
BinaryFormatter binaryFormatter = new BinaryFormatter();
var result = (CacheItem)binaryFormatter.Deserialize(stream);
return result;
}
private T Retry<T>(Func<T> action) {
var retries = 3;
for (int i = 1; i <= retries; i++) {
try {
var t = action();
return t;
}
catch {
if (i == retries) {
throw;
}
}
}
return default(T);
}
private void Retry(Action action) {
var retries = 3;
for(int i=1; i <= retries; i++) {
try {
action();
}
catch {
if(i == retries) {
throw;
}
}
}
}
}
}