mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: 282fdc30f4a813fe8fe759a21debfb6142676f52 Former-commit-id: 39a6b41dea2323461008da07120c387169913523pull/17/head
11 changed files with 689 additions and 103 deletions
@ -0,0 +1,98 @@ |
|||||
|
namespace ImageProcessor.Web.AzureBlobCache |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Configuration; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Web; |
||||
|
|
||||
|
using ImageProcessor.Web.Caching; |
||||
|
|
||||
|
using Microsoft.WindowsAzure; |
||||
|
using Microsoft.WindowsAzure.Storage; |
||||
|
using Microsoft.WindowsAzure.Storage.Blob; |
||||
|
|
||||
|
public class AzureBlobCache : ImageCacheBase |
||||
|
{ |
||||
|
private CloudStorageAccount cloudStorageAccount; |
||||
|
|
||||
|
private CloudBlobClient cloudBlobClient; |
||||
|
|
||||
|
private CloudBlobContainer cloudBlobContainer; |
||||
|
|
||||
|
public AzureBlobCache(string requestPath, string fullPath, string querystring) |
||||
|
: base(requestPath, fullPath, querystring) |
||||
|
{ |
||||
|
// TODO: These should all be in the configuration.
|
||||
|
|
||||
|
// Retrieve storage account from connection string.
|
||||
|
this.cloudStorageAccount = CloudStorageAccount.Parse( |
||||
|
CloudConfigurationManager.GetSetting("StorageConnectionString")); |
||||
|
|
||||
|
// Create the blob client.
|
||||
|
this.cloudBlobClient = this.cloudStorageAccount.CreateCloudBlobClient(); |
||||
|
|
||||
|
// Retrieve reference to a previously created container.
|
||||
|
this.cloudBlobContainer = this.cloudBlobClient.GetContainerReference("mycontainer"); |
||||
|
} |
||||
|
|
||||
|
public override int MaxAge |
||||
|
{ |
||||
|
get { throw new System.NotImplementedException(); } |
||||
|
} |
||||
|
|
||||
|
public override async Task<bool> IsNewOrUpdatedAsync() |
||||
|
{ |
||||
|
string cachedFileName = await this.CreateCachedFileName(); |
||||
|
|
||||
|
// TODO: Generate cache path.
|
||||
|
CloudBlockBlob blockBlob = new CloudBlockBlob(new Uri("")); |
||||
|
|
||||
|
bool isUpdated = false; |
||||
|
if (!await blockBlob.ExistsAsync()) |
||||
|
{ |
||||
|
// Nothing in the cache so we should return true.
|
||||
|
isUpdated = true; |
||||
|
} |
||||
|
else if (blockBlob.Properties.LastModified.HasValue) |
||||
|
{ |
||||
|
// Check to see if the cached image is set to expire.
|
||||
|
if (this.IsExpired(blockBlob.Properties.LastModified.Value.UtcDateTime)) |
||||
|
{ |
||||
|
isUpdated = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return isUpdated; |
||||
|
} |
||||
|
|
||||
|
public override async Task AddImageToCacheAsync(System.IO.Stream stream) |
||||
|
{ |
||||
|
throw new System.NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public override async Task TrimCacheAsync() |
||||
|
{ |
||||
|
throw new System.NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public override void RewritePath(HttpContext context) |
||||
|
{ |
||||
|
throw new System.NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the given images creation date is out with
|
||||
|
/// the prescribed limit.
|
||||
|
/// </summary>
|
||||
|
/// <param name="creationDate">
|
||||
|
/// The creation date.
|
||||
|
/// </param>
|
||||
|
/// <returns>
|
||||
|
/// The true if the date is out with the limit, otherwise; false.
|
||||
|
/// </returns>
|
||||
|
private bool IsExpired(DateTime creationDate) |
||||
|
{ |
||||
|
return creationDate.AddDays(this.MaxAge) < DateTime.UtcNow.AddDays(-this.MaxAge); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> |
||||
|
<PropertyGroup> |
||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
||||
|
<ProjectGuid>{3C805E4C-D679-43F8-8C43-8909CDB4D4D7}</ProjectGuid> |
||||
|
<OutputType>Library</OutputType> |
||||
|
<AppDesignerFolder>Properties</AppDesignerFolder> |
||||
|
<RootNamespace>ImageProcessor.Web.AzureBlobCache</RootNamespace> |
||||
|
<AssemblyName>ImageProcessor.Web.AzureBlobCache</AssemblyName> |
||||
|
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> |
||||
|
<FileAlignment>512</FileAlignment> |
||||
|
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir> |
||||
|
<RestorePackages>true</RestorePackages> |
||||
|
</PropertyGroup> |
||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
||||
|
<DebugSymbols>true</DebugSymbols> |
||||
|
<DebugType>full</DebugType> |
||||
|
<Optimize>false</Optimize> |
||||
|
<OutputPath>bin\Debug\</OutputPath> |
||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants> |
||||
|
<ErrorReport>prompt</ErrorReport> |
||||
|
<WarningLevel>4</WarningLevel> |
||||
|
</PropertyGroup> |
||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
||||
|
<DebugType>pdbonly</DebugType> |
||||
|
<Optimize>true</Optimize> |
||||
|
<OutputPath>bin\Release\</OutputPath> |
||||
|
<DefineConstants>TRACE</DefineConstants> |
||||
|
<ErrorReport>prompt</ErrorReport> |
||||
|
<WarningLevel>4</WarningLevel> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<Reference Include="Microsoft.Data.Edm"> |
||||
|
<HintPath>..\packages\Microsoft.Data.Edm.5.6.2\lib\net40\Microsoft.Data.Edm.dll</HintPath> |
||||
|
<Private>True</Private> |
||||
|
</Reference> |
||||
|
<Reference Include="Microsoft.Data.OData"> |
||||
|
<HintPath>..\packages\Microsoft.Data.OData.5.6.2\lib\net40\Microsoft.Data.OData.dll</HintPath> |
||||
|
<Private>True</Private> |
||||
|
</Reference> |
||||
|
<Reference Include="Microsoft.Data.Services.Client, Version=5.6.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> |
||||
|
<SpecificVersion>False</SpecificVersion> |
||||
|
<HintPath>..\packages\Microsoft.Data.Services.Client.5.6.2\lib\net40\Microsoft.Data.Services.Client.dll</HintPath> |
||||
|
</Reference> |
||||
|
<Reference Include="Microsoft.WindowsAzure.Configuration"> |
||||
|
<HintPath>..\packages\Microsoft.WindowsAzure.ConfigurationManager.1.8.0.0\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll</HintPath> |
||||
|
</Reference> |
||||
|
<Reference Include="Newtonsoft.Json"> |
||||
|
<HintPath>..\packages\Newtonsoft.Json.5.0.8\lib\net45\Newtonsoft.Json.dll</HintPath> |
||||
|
<Private>True</Private> |
||||
|
</Reference> |
||||
|
<Reference Include="System" /> |
||||
|
<Reference Include="System.Core" /> |
||||
|
<Reference Include="System.Spatial, Version=5.6.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> |
||||
|
<SpecificVersion>False</SpecificVersion> |
||||
|
<HintPath>..\packages\System.Spatial.5.6.2\lib\net40\System.Spatial.dll</HintPath> |
||||
|
</Reference> |
||||
|
<Reference Include="System.Web" /> |
||||
|
<Reference Include="System.Xml.Linq" /> |
||||
|
<Reference Include="System.Data.DataSetExtensions" /> |
||||
|
<Reference Include="Microsoft.CSharp" /> |
||||
|
<Reference Include="System.Data" /> |
||||
|
<Reference Include="System.Xml" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<Compile Include="AzureBlobCache.cs" /> |
||||
|
<Compile Include="Properties\AssemblyInfo.cs" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\ImageProcessor.Web\ImageProcessor.Web.csproj"> |
||||
|
<Project>{d011a778-59c8-4bfa-a770-c350216bf161}</Project> |
||||
|
<Name>ImageProcessor.Web</Name> |
||||
|
</ProjectReference> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<None Include="packages.config" /> |
||||
|
</ItemGroup> |
||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
||||
|
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> |
||||
|
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> |
||||
|
<PropertyGroup> |
||||
|
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> |
||||
|
</PropertyGroup> |
||||
|
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" /> |
||||
|
</Target> |
||||
|
<!-- 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"> |
||||
|
</Target> |
||||
|
--> |
||||
|
</Project> |
||||
@ -0,0 +1,36 @@ |
|||||
|
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("ImageProcessor.Web.AzureBlobCache")] |
||||
|
[assembly: AssemblyDescription("")] |
||||
|
[assembly: AssemblyConfiguration("")] |
||||
|
[assembly: AssemblyCompany("")] |
||||
|
[assembly: AssemblyProduct("ImageProcessor.Web.AzureBlobCache")] |
||||
|
[assembly: AssemblyCopyright("Copyright © 2015")] |
||||
|
[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("90605e94-25f4-4c69-b602-6b1df0adb89e")] |
||||
|
|
||||
|
// 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 Build and Revision Numbers
|
||||
|
// by using the '*' as shown below:
|
||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||
|
[assembly: AssemblyVersion("1.0.0.0")] |
||||
|
[assembly: AssemblyFileVersion("1.0.0.0")] |
||||
@ -0,0 +1,10 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<packages> |
||||
|
<package id="Microsoft.Data.Edm" version="5.6.2" targetFramework="net45" /> |
||||
|
<package id="Microsoft.Data.OData" version="5.6.2" targetFramework="net45" /> |
||||
|
<package id="Microsoft.Data.Services.Client" version="5.6.2" targetFramework="net45" /> |
||||
|
<package id="Microsoft.WindowsAzure.ConfigurationManager" version="1.8.0.0" targetFramework="net45" /> |
||||
|
<package id="Newtonsoft.Json" version="5.0.8" targetFramework="net45" /> |
||||
|
<package id="System.Spatial" version="5.6.2" targetFramework="net45" /> |
||||
|
<package id="WindowsAzure.Storage" version="4.3.0" targetFramework="net45" /> |
||||
|
</packages> |
||||
@ -0,0 +1,180 @@ |
|||||
|
namespace ImageProcessor.Web.Caching |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Web; |
||||
|
using System.Web.Hosting; |
||||
|
|
||||
|
using ImageProcessor.Web.Configuration; |
||||
|
using ImageProcessor.Web.Extensions; |
||||
|
|
||||
|
public class DiskCache2 : ImageCacheBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The maximum number of files allowed in the directory.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// NTFS directories can handle up to 10,000 files in the directory before slowing down.
|
||||
|
/// This will help us to ensure that don't go over that limit.
|
||||
|
/// <see href="http://stackoverflow.com/questions/197162/ntfs-performance-and-large-volumes-of-files-and-directories"/>
|
||||
|
/// <see href="http://stackoverflow.com/questions/115882/how-do-you-deal-with-lots-of-small-files"/>
|
||||
|
/// <see href="http://stackoverflow.com/questions/1638219/millions-of-small-graphics-files-and-how-to-overcome-slow-file-system-access-on"/>
|
||||
|
/// </remarks>
|
||||
|
private const int MaxFilesCount = 100; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The virtual cache path.
|
||||
|
/// </summary>
|
||||
|
private static readonly string VirtualCachePath = ImageProcessorConfiguration.Instance.VirtualCachePath; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The absolute path to virtual cache path on the server.
|
||||
|
/// TODO: Change this so configuration is determined per IImageCache instance.
|
||||
|
/// </summary>
|
||||
|
private static readonly string AbsoluteCachePath = HostingEnvironment.MapPath(VirtualCachePath); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The physical cached path.
|
||||
|
/// </summary>
|
||||
|
private string physicalCachedPath; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The virtual cached path.
|
||||
|
/// </summary>
|
||||
|
private string virtualCachedPath; |
||||
|
|
||||
|
public DiskCache2(string requestPath, string fullPath, string querystring) |
||||
|
: base(requestPath, fullPath, querystring) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The maximum number of days to cache files on the system for.
|
||||
|
/// TODO: Shift the getter source to proper config.
|
||||
|
/// </summary>
|
||||
|
public override int MaxAge |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return ImageProcessorConfiguration.Instance.MaxCacheDays; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override async Task<bool> IsNewOrUpdatedAsync() |
||||
|
{ |
||||
|
string cachedFileName = await this.CreateCachedFileName(); |
||||
|
|
||||
|
// Collision rate of about 1 in 10000 for the folder structure.
|
||||
|
// That gives us massive scope for files.
|
||||
|
string pathFromKey = string.Join("\\", cachedFileName.ToCharArray().Take(6)); |
||||
|
string virtualPathFromKey = pathFromKey.Replace(@"\", "/"); |
||||
|
this.physicalCachedPath = Path.Combine(AbsoluteCachePath, pathFromKey, cachedFileName); |
||||
|
this.virtualCachedPath = Path.Combine(VirtualCachePath, virtualPathFromKey, cachedFileName).Replace(@"\", "/"); |
||||
|
this.CachedPath = this.physicalCachedPath; |
||||
|
|
||||
|
bool isUpdated = false; |
||||
|
CachedImage cachedImage = CacheIndexer.GetValue(this.physicalCachedPath); |
||||
|
|
||||
|
if (cachedImage == null) |
||||
|
{ |
||||
|
// Nothing in the cache so we should return true.
|
||||
|
isUpdated = true; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Check to see if the cached image is set to expire.
|
||||
|
if (this.IsExpired(cachedImage.CreationTimeUtc)) |
||||
|
{ |
||||
|
CacheIndexer.Remove(this.physicalCachedPath); |
||||
|
isUpdated = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return isUpdated; |
||||
|
} |
||||
|
|
||||
|
public override async Task AddImageToCacheAsync(Stream stream) |
||||
|
{ |
||||
|
// ReSharper disable once AssignNullToNotNullAttribute
|
||||
|
DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetDirectoryName(this.physicalCachedPath)); |
||||
|
if (!directoryInfo.Exists) |
||||
|
{ |
||||
|
directoryInfo.Create(); |
||||
|
} |
||||
|
|
||||
|
using (FileStream fileStream = File.Create(this.physicalCachedPath)) |
||||
|
{ |
||||
|
await stream.CopyToAsync(fileStream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override async Task TrimCacheAsync() |
||||
|
{ |
||||
|
string directory = Path.GetDirectoryName(this.physicalCachedPath); |
||||
|
|
||||
|
if (directory != null) |
||||
|
{ |
||||
|
DirectoryInfo directoryInfo = new DirectoryInfo(directory); |
||||
|
DirectoryInfo parentDirectoryInfo = directoryInfo.Parent; |
||||
|
|
||||
|
if (parentDirectoryInfo != null) |
||||
|
{ |
||||
|
// UNC folders can throw exceptions if the file doesn't exist.
|
||||
|
foreach (DirectoryInfo enumerateDirectory in await parentDirectoryInfo.SafeEnumerateDirectoriesAsync()) |
||||
|
{ |
||||
|
IEnumerable<FileInfo> files = enumerateDirectory.EnumerateFiles().OrderBy(f => f.CreationTimeUtc); |
||||
|
int count = files.Count(); |
||||
|
|
||||
|
foreach (FileInfo fileInfo in files) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
// If the group count is equal to the max count minus 1 then we know we
|
||||
|
// have reduced the number of items below the maximum allowed.
|
||||
|
// We'll cleanup any orphaned expired files though.
|
||||
|
if (!this.IsExpired(fileInfo.CreationTimeUtc) && count <= MaxFilesCount - 1) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
// Remove from the cache and delete each CachedImage.
|
||||
|
CacheIndexer.Remove(fileInfo.Name); |
||||
|
fileInfo.Delete(); |
||||
|
count -= 1; |
||||
|
} |
||||
|
// ReSharper disable once EmptyGeneralCatchClause
|
||||
|
catch |
||||
|
{ |
||||
|
// Do nothing; skip to the next file.
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override void RewritePath(HttpContext context) |
||||
|
{ |
||||
|
// The cached file is valid so just rewrite the path.
|
||||
|
context.RewritePath(this.virtualCachedPath, false); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the given images creation date is out with
|
||||
|
/// the prescribed limit.
|
||||
|
/// </summary>
|
||||
|
/// <param name="creationDate">
|
||||
|
/// The creation date.
|
||||
|
/// </param>
|
||||
|
/// <returns>
|
||||
|
/// The true if the date is out with the limit, otherwise; false.
|
||||
|
/// </returns>
|
||||
|
private bool IsExpired(DateTime creationDate) |
||||
|
{ |
||||
|
return creationDate.AddDays(this.MaxAge) < DateTime.UtcNow.AddDays(-this.MaxAge); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
|
||||
|
namespace ImageProcessor.Web.Caching |
||||
|
{ |
||||
|
using System.IO; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Web; |
||||
|
|
||||
|
public interface IImageCache |
||||
|
{ |
||||
|
string CachedPath { get; } |
||||
|
|
||||
|
int MaxAge { get; } |
||||
|
|
||||
|
Task<bool> IsNewOrUpdatedAsync(); |
||||
|
|
||||
|
Task AddImageToCacheAsync(Stream stream); |
||||
|
|
||||
|
Task TrimCacheAsync(); |
||||
|
|
||||
|
Task<string> CreateCachedFileName(); |
||||
|
|
||||
|
void RewritePath(HttpContext context); |
||||
|
|
||||
|
//void SetHeaders(HttpContext context);
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,125 @@ |
|||||
|
namespace ImageProcessor.Web.Caching |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using System.IO; |
||||
|
using System.Reflection; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Web; |
||||
|
|
||||
|
using ImageProcessor.Web.Extensions; |
||||
|
using ImageProcessor.Web.Helpers; |
||||
|
|
||||
|
public abstract class ImageCacheBase : IImageCache |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The assembly version.
|
||||
|
/// </summary>
|
||||
|
private static readonly string AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The request path for the image.
|
||||
|
/// </summary>
|
||||
|
private readonly string requestPath; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The full path for the image.
|
||||
|
/// </summary>
|
||||
|
private readonly string fullPath; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The querystring containing processing instructions.
|
||||
|
/// </summary>
|
||||
|
private readonly string querystring; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ImageCacheBase"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="requestPath">
|
||||
|
/// The request path for the image.
|
||||
|
/// </param>
|
||||
|
/// <param name="fullPath">
|
||||
|
/// The full path for the image.
|
||||
|
/// </param>
|
||||
|
/// <param name="querystring">
|
||||
|
/// The querystring containing instructions.
|
||||
|
/// </param>
|
||||
|
protected ImageCacheBase(string requestPath, string fullPath, string querystring) |
||||
|
{ |
||||
|
this.requestPath = requestPath; |
||||
|
this.fullPath = fullPath; |
||||
|
this.querystring = querystring; |
||||
|
} |
||||
|
|
||||
|
public string CachedPath { get; protected set; } |
||||
|
|
||||
|
public abstract int MaxAge { get; } |
||||
|
|
||||
|
public abstract Task<bool> IsNewOrUpdatedAsync(); |
||||
|
|
||||
|
public abstract Task AddImageToCacheAsync(Stream stream); |
||||
|
|
||||
|
public abstract Task TrimCacheAsync(); |
||||
|
|
||||
|
public Task<string> CreateCachedFileName() |
||||
|
{ |
||||
|
string streamHash = string.Empty; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
if (new Uri(this.requestPath).IsFile) |
||||
|
{ |
||||
|
// Get the hash for the filestream. That way we can ensure that if the image is
|
||||
|
// updated but has the same name we will know.
|
||||
|
FileInfo imageFileInfo = new FileInfo(this.requestPath); |
||||
|
if (imageFileInfo.Exists) |
||||
|
{ |
||||
|
// Pull the latest info.
|
||||
|
imageFileInfo.Refresh(); |
||||
|
|
||||
|
// Checking the stream itself is far too processor intensive so we make a best guess.
|
||||
|
string creation = imageFileInfo.CreationTimeUtc.ToString(CultureInfo.InvariantCulture); |
||||
|
string length = imageFileInfo.Length.ToString(CultureInfo.InvariantCulture); |
||||
|
streamHash = string.Format("{0}{1}", creation, length); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
streamHash = string.Empty; |
||||
|
} |
||||
|
|
||||
|
// Use an sha1 hash of the full path including the querystring to create the image name.
|
||||
|
// That name can also be used as a key for the cached image and we should be able to use
|
||||
|
// The characters of that hash as sub-folders.
|
||||
|
string parsedExtension = ImageHelpers.GetExtension(this.fullPath, this.querystring); |
||||
|
string encryptedName = (streamHash + this.fullPath).ToSHA1Fingerprint(); |
||||
|
|
||||
|
string cachedFileName = string.Format( |
||||
|
"{0}.{1}", |
||||
|
encryptedName, |
||||
|
!string.IsNullOrWhiteSpace(parsedExtension) ? parsedExtension.Replace(".", string.Empty) : "jpg"); |
||||
|
|
||||
|
this.CachedPath = cachedFileName; |
||||
|
return Task.FromResult(cachedFileName); |
||||
|
} |
||||
|
|
||||
|
public abstract void RewritePath(HttpContext context); |
||||
|
|
||||
|
public virtual void SetHeaders(HttpContext context, string responseType) |
||||
|
{ |
||||
|
HttpResponse response = context.Response; |
||||
|
|
||||
|
response.ContentType = responseType; |
||||
|
|
||||
|
if (response.Headers["Image-Served-By"] == null) |
||||
|
{ |
||||
|
response.AddHeader("Image-Served-By", "ImageProcessor.Web/" + AssemblyVersion); |
||||
|
} |
||||
|
|
||||
|
HttpCachePolicy cache = response.Cache; |
||||
|
cache.SetCacheability(HttpCacheability.Public); |
||||
|
cache.VaryByHeaders["Accept-Encoding"] = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,6 +1,7 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<repositories> |
<repositories> |
||||
<repository path="..\ImageProcessor.UnitTests\packages.config" /> |
<repository path="..\ImageProcessor.UnitTests\packages.config" /> |
||||
|
<repository path="..\ImageProcessor.Web.AzureBlobCache\packages.config" /> |
||||
<repository path="..\ImageProcessor.Web.UnitTests\packages.config" /> |
<repository path="..\ImageProcessor.Web.UnitTests\packages.config" /> |
||||
<repository path="..\TestWebsites\MVC\packages.config" /> |
<repository path="..\TestWebsites\MVC\packages.config" /> |
||||
</repositories> |
</repositories> |
||||
Loading…
Reference in new issue