diff --git a/src/ImageProcessor.Playground/ImageProcessor.Playground.csproj b/src/ImageProcessor.Playground/ImageProcessor.Playground.csproj index 44d73c00b..0c659620c 100644 --- a/src/ImageProcessor.Playground/ImageProcessor.Playground.csproj +++ b/src/ImageProcessor.Playground/ImageProcessor.Playground.csproj @@ -55,6 +55,14 @@ + + {3c805e4c-d679-43f8-8c43-8909cdb4d4d7} + ImageProcessor.Web.AzureBlobCache + + + {d011a778-59c8-4bfa-a770-c350216bf161} + ImageProcessor.Web + {3B5DD734-FB7A-487D-8CE6-55E7AF9AEA7E} ImageProcessor diff --git a/src/ImageProcessor.Playground/Program.cs b/src/ImageProcessor.Playground/Program.cs index 937909347..e829732d8 100644 --- a/src/ImageProcessor.Playground/Program.cs +++ b/src/ImageProcessor.Playground/Program.cs @@ -24,6 +24,7 @@ namespace ImageProcessor.PlayGround using ImageProcessor.Imaging.Filters.Photo; using ImageProcessor.Imaging.Formats; using ImageProcessor.Processors; + using ImageProcessor.Web.Caching; /// /// The program. @@ -38,6 +39,10 @@ namespace ImageProcessor.PlayGround /// public static void Main(string[] args) { + var x = typeof(AzureBlobCache); + Console.WriteLine(x.AssemblyQualifiedName); + Console.ReadLine(); + string path = new Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath; // ReSharper disable once AssignNullToNotNullAttribute diff --git a/src/ImageProcessor.Web.AzureBlobCache/AzureBlobCache.cs b/src/ImageProcessor.Web.AzureBlobCache/AzureBlobCache.cs index 3820f421a..72c23c5ea 100644 --- a/src/ImageProcessor.Web.AzureBlobCache/AzureBlobCache.cs +++ b/src/ImageProcessor.Web.AzureBlobCache/AzureBlobCache.cs @@ -1,19 +1,17 @@ -namespace ImageProcessor.Web.AzureBlobCache +namespace ImageProcessor.Web.Caching { using System; using System.Collections.Generic; - using System.Configuration; using System.Globalization; using System.IO; using System.Linq; + using System.Net; using System.Threading.Tasks; using System.Web; - using ImageProcessor.Web.Caching; using ImageProcessor.Web.Extensions; using ImageProcessor.Web.Helpers; - using Microsoft.WindowsAzure; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Blob; @@ -36,7 +34,9 @@ private CloudBlobContainer cloudSourceBlobContainer; - private string cachedContainerRoot; + private string cachedCDNRoot; + + private string cachedRewritePath; /// /// The physical cached path. @@ -60,7 +60,7 @@ this.cloudCachedBlobContainer = this.cloudCachedBlobClient.GetContainerReference(this.Settings["CachedBlobContainer"]); this.cloudSourceBlobContainer = this.cloudSourceBlobClient.GetContainerReference(this.Settings["SourceBlobContainer"]); - this.cachedContainerRoot = this.Settings["CachedContainerRoot"]; + this.cachedCDNRoot = this.Settings["CachedCDNRoot"]; } public override int MaxDays @@ -78,15 +78,36 @@ // Collision rate of about 1 in 10000 for the folder structure. // That gives us massive scope to store millions of files. string pathFromKey = string.Join("\\", cachedFileName.ToCharArray().Take(6)); - this.CachedPath = Path.Combine(this.cachedContainerRoot, pathFromKey, cachedFileName).Replace(@"\", "/"); + this.CachedPath = Path.Combine(this.cloudCachedBlobContainer.Uri.ToString(), pathFromKey, cachedFileName).Replace(@"\", "/"); + this.cachedRewritePath = Path.Combine(this.cachedCDNRoot, this.cloudCachedBlobContainer.Name, pathFromKey, cachedFileName).Replace(@"\", "/"); bool isUpdated = false; CachedImage cachedImage = CacheIndexer.GetValue(this.CachedPath); + if (new Uri(this.CachedPath).IsFile) + { + FileInfo fileInfo = new FileInfo(this.CachedPath); + + if (fileInfo.Exists) + { + // Pull the latest info. + fileInfo.Refresh(); + + cachedImage = new CachedImage + { + Key = Path.GetFileNameWithoutExtension(this.CachedPath), + Path = this.CachedPath, + CreationTimeUtc = fileInfo.CreationTimeUtc + }; + + CacheIndexer.Add(cachedImage); + } + } + if (cachedImage == null) { - ICloudBlob blockBlob = - await this.cloudCachedBlobContainer.GetBlobReferenceFromServerAsync(this.RequestPath); + string blobPath = this.CachedPath.Substring(this.cloudCachedBlobContainer.Uri.ToString().Length + 1); + CloudBlockBlob blockBlob = this.cloudCachedBlobContainer.GetBlockBlobReference(blobPath); if (await blockBlob.ExistsAsync()) { @@ -106,21 +127,21 @@ CacheIndexer.Add(cachedImage); } } + } - if (cachedImage == null) + 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)) { - // Nothing in the cache so we should return true. + CacheIndexer.Remove(this.CachedPath); isUpdated = true; } - else - { - // Check to see if the cached image is set to expire. - if (this.IsExpired(cachedImage.CreationTimeUtc)) - { - CacheIndexer.Remove(this.CachedPath); - isUpdated = true; - } - } } return isUpdated; @@ -128,7 +149,8 @@ public override async Task AddImageToCacheAsync(Stream stream) { - CloudBlockBlob blockBlob = this.cloudCachedBlobContainer.GetBlockBlobReference(this.CachedPath); + string blobPath = this.CachedPath.Substring(this.cloudCachedBlobContainer.Uri.ToString().Length + 1); + CloudBlockBlob blockBlob = this.cloudCachedBlobContainer.GetBlockBlobReference(blobPath); await blockBlob.UploadFromStreamAsync(stream); } @@ -137,7 +159,7 @@ Uri uri = new Uri(this.CachedPath); string path = uri.GetLeftPart(UriPartial.Path); string directory = path.Substring(0, path.LastIndexOf('/')); - string parent = directory.Substring(0, path.LastIndexOf('/')); + string parent = directory.Substring(this.cloudCachedBlobContainer.Uri.ToString().Length + 1, path.LastIndexOf('/')); BlobContinuationToken continuationToken = null; CloudBlobDirectory directoryBlob = this.cloudCachedBlobContainer.GetDirectoryReference(parent); @@ -163,12 +185,10 @@ { break; } - else - { - // Remove from the cache and delete each CachedImage. - CacheIndexer.Remove(blob.Name); - await blob.DeleteAsync(); - } + + // Remove from the cache and delete each CachedImage. + CacheIndexer.Remove(blob.Name); + await blob.DeleteAsync(); } } @@ -180,8 +200,24 @@ { if (new Uri(this.RequestPath).IsFile) { - ICloudBlob blockBlob = await this.cloudSourceBlobContainer - .GetBlobReferenceFromServerAsync(this.RequestPath); + // 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); + } + } + else + { + string blobPath = this.CachedPath.Substring(this.cloudSourceBlobContainer.Uri.ToString().Length + 1); + CloudBlockBlob blockBlob = this.cloudSourceBlobContainer.GetBlockBlobReference(blobPath); if (await blockBlob.ExistsAsync()) { @@ -190,24 +226,11 @@ if (blockBlob.Properties.LastModified.HasValue) { - string creation = blockBlob.Properties.LastModified.Value.UtcDateTime.ToString(CultureInfo.InvariantCulture); - string length = blockBlob.Properties.Length.ToString(CultureInfo.InvariantCulture); - streamHash = string.Format("{0}{1}", creation, length); - } - } - else - { - // 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(); + string creation = blockBlob.Properties + .LastModified.Value.UtcDateTime + .ToString(CultureInfo.InvariantCulture); - // 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); + string length = blockBlob.Properties.Length.ToString(CultureInfo.InvariantCulture); streamHash = string.Format("{0}{1}", creation, length); } } @@ -234,8 +257,16 @@ public override void RewritePath(HttpContext context) { - // The cached file is valid so just rewrite the path. - context.RewritePath(this.CachedPath, false); + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.cachedRewritePath); + request.Method = "HEAD"; + + using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) + { + HttpStatusCode responseCode = response.StatusCode; + context.Response.Redirect( + responseCode == HttpStatusCode.NotFound ? this.CachedPath : this.cachedRewritePath, + false); + } } } } diff --git a/src/ImageProcessor.Web.AzureBlobCache/ImageProcessor.Web.AzureBlobCache.csproj b/src/ImageProcessor.Web.AzureBlobCache/ImageProcessor.Web.AzureBlobCache.csproj index f2b15b3ab..6e57a2a38 100644 --- a/src/ImageProcessor.Web.AzureBlobCache/ImageProcessor.Web.AzureBlobCache.csproj +++ b/src/ImageProcessor.Web.AzureBlobCache/ImageProcessor.Web.AzureBlobCache.csproj @@ -7,8 +7,8 @@ {3C805E4C-D679-43F8-8C43-8909CDB4D4D7} Library Properties - ImageProcessor.Web.AzureBlobCache - ImageProcessor.Web.AzureBlobCache + ImageProcessor.Web.Caching + ImageProcessor.Web.Caching.AzureBlobCache v4.5 512 ..\ diff --git a/src/ImageProcessor.Web.PostProcessor/PostProcessor.cs b/src/ImageProcessor.Web.PostProcessor/PostProcessor.cs index 9d3a2be70..4144aea5b 100644 --- a/src/ImageProcessor.Web.PostProcessor/PostProcessor.cs +++ b/src/ImageProcessor.Web.PostProcessor/PostProcessor.cs @@ -34,6 +34,11 @@ namespace ImageProcessor.Web.PostProcessor /// public static async Task PostProcessImageAsync(string sourceFile) { + if (!new Uri(sourceFile).IsFile) + { + return; + } + string targetFile = Path.GetTempFileName(); PostProcessingResultEventArgs result = await RunProcess(sourceFile, targetFile); diff --git a/src/ImageProcessor.Web/Caching/CacheIndexer.cs b/src/ImageProcessor.Web/Caching/CacheIndexer.cs index 8a7786eb3..39654e83a 100644 --- a/src/ImageProcessor.Web/Caching/CacheIndexer.cs +++ b/src/ImageProcessor.Web/Caching/CacheIndexer.cs @@ -10,6 +10,7 @@ namespace ImageProcessor.Web.Caching { + using System; using System.Collections.Generic; using System.IO; using System.Runtime.Caching; @@ -64,11 +65,19 @@ namespace ImageProcessor.Web.Caching /// public static CachedImage Add(CachedImage cachedImage) { - // Add the CachedImage. - CacheItemPolicy policy = new CacheItemPolicy(); - policy.ChangeMonitors.Add(new HostFileChangeMonitor(new List { cachedImage.Path })); + if (new Uri(cachedImage.Path).IsFile) + { + // Add the CachedImage. + CacheItemPolicy policy = new CacheItemPolicy(); + policy.ChangeMonitors.Add(new HostFileChangeMonitor(new List { cachedImage.Path })); + + MemCache.AddItem(Path.GetFileNameWithoutExtension(cachedImage.Key), cachedImage, policy); + } + else + { + MemCache.AddItem(Path.GetFileNameWithoutExtension(cachedImage.Key), cachedImage); + } - MemCache.AddItem(Path.GetFileNameWithoutExtension(cachedImage.Key), cachedImage, policy); return cachedImage; } #endregion diff --git a/src/ImageProcessor.Web/Configuration/ImageCacheSection.cs b/src/ImageProcessor.Web/Configuration/ImageCacheSection.cs index 2aa05cc04..7847978f5 100644 --- a/src/ImageProcessor.Web/Configuration/ImageCacheSection.cs +++ b/src/ImageProcessor.Web/Configuration/ImageCacheSection.cs @@ -59,7 +59,7 @@ namespace ImageProcessor.Web.Configuration /// The cache configuration section from the current application configuration. public static ImageCacheSection GetConfiguration() { - ImageCacheSection imageCacheSection = ConfigurationManager.GetSection("imageProcessor/cache") as ImageCacheSection; + ImageCacheSection imageCacheSection = ConfigurationManager.GetSection("imageProcessor/caching") as ImageCacheSection; if (imageCacheSection != null) { diff --git a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs index caf79a5b6..160d3d48e 100644 --- a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs @@ -425,34 +425,6 @@ namespace ImageProcessor.Web.HttpModules } } - // Image is from the cache so the mime-type will need to be set. - // TODO: Is this bit needed? Is the static file handler doing stuff for the filecache - // but not others. - if (context.Items[CachedResponseTypeKey] == null) - { - string mimetype = ImageHelpers.GetMimeType(this.imageCache.CachedPath); - - if (!string.IsNullOrEmpty(mimetype)) - { - context.Items[CachedResponseTypeKey] = mimetype; - } - } - - if (context.Items[CachedResponseFileDependency] == null) - { - if (isFileLocal) - { - // Some services might only provide filename so we can't monitor for the browser. - context.Items[CachedResponseFileDependency] = Path.GetFileName(requestPath) == requestPath - ? new List { this.imageCache.CachedPath } - : new List { requestPath, this.imageCache.CachedPath }; - } - else - { - context.Items[CachedResponseFileDependency] = new List { this.imageCache.CachedPath }; - } - } - // The cached file is valid so just rewrite the path. this.imageCache.RewritePath(context); } diff --git a/src/TestWebsites/MVC/Views/Home/Index.cshtml b/src/TestWebsites/MVC/Views/Home/Index.cshtml index bceabd8ff..39dc3930e 100644 --- a/src/TestWebsites/MVC/Views/Home/Index.cshtml +++ b/src/TestWebsites/MVC/Views/Home/Index.cshtml @@ -7,24 +7,24 @@

Resized

- -

Foreign language test.

+ + @*

Foreign language test.

Strange name

- + *@
-
+ @*

Cropped

Cropped Percent

-
+
*@
-
+ @*

Reside Pad

@@ -199,9 +199,9 @@
-
+
*@ -
+@*

Color Profiles

@@ -223,4 +223,4 @@
-
\ No newline at end of file +
*@ \ No newline at end of file diff --git a/src/TestWebsites/MVC/Web.config b/src/TestWebsites/MVC/Web.config index eb09cc6d7..136c9c7fa 100644 --- a/src/TestWebsites/MVC/Web.config +++ b/src/TestWebsites/MVC/Web.config @@ -9,12 +9,12 @@
-
+
- +