mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
Adding a file system backed output cache provider
This commit is contained in:
@@ -7,9 +7,16 @@ OrchardVersion: 1.9
|
|||||||
Description: Adds output caching functionality
|
Description: Adds output caching functionality
|
||||||
Features:
|
Features:
|
||||||
Orchard.OutputCache:
|
Orchard.OutputCache:
|
||||||
|
Name: Output Cache
|
||||||
Description: Adds output caching functionality.
|
Description: Adds output caching functionality.
|
||||||
Category: Performance
|
Category: Performance
|
||||||
Orchard.OutputCache.Database:
|
Orchard.OutputCache.Database:
|
||||||
|
Name: Database Output Cache
|
||||||
Description: Activates a provider that stores output cache data in the database.
|
Description: Activates a provider that stores output cache data in the database.
|
||||||
Category: Performance
|
Category: Performance
|
||||||
Dependencies: Orchard.OutputCache
|
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
|
||||||
|
|||||||
@@ -107,7 +107,9 @@
|
|||||||
<Compile Include="Models\CacheSettingsPart.cs" />
|
<Compile Include="Models\CacheSettingsPart.cs" />
|
||||||
<Compile Include="Models\CacheParameterRecord.cs" />
|
<Compile Include="Models\CacheParameterRecord.cs" />
|
||||||
<Compile Include="Services\CacheService.cs" />
|
<Compile Include="Services\CacheService.cs" />
|
||||||
|
<Compile Include="Services\FileSystemOutputCacheBackgroundTask.cs" />
|
||||||
<Compile Include="Services\DatabaseOutputCacheBackgroundTask.cs" />
|
<Compile Include="Services\DatabaseOutputCacheBackgroundTask.cs" />
|
||||||
|
<Compile Include="Services\FileSystemOutputCacheProvider.cs" />
|
||||||
<Compile Include="Services\DatabaseOutputCacheProvider.cs" />
|
<Compile Include="Services\DatabaseOutputCacheProvider.cs" />
|
||||||
<Compile Include="Services\DefaultCacheControlStrategy.cs" />
|
<Compile Include="Services\DefaultCacheControlStrategy.cs" />
|
||||||
<Compile Include="Services\DefaultTagCache.cs" />
|
<Compile Include="Services\DefaultTagCache.cs" />
|
||||||
|
|||||||
+56
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+165
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user