diff --git a/src/ImageProcessor.Web/Caching/DiskCache.cs b/src/ImageProcessor.Web/Caching/DiskCache.cs index 8fb69810b..c0ebbe2bc 100644 --- a/src/ImageProcessor.Web/Caching/DiskCache.cs +++ b/src/ImageProcessor.Web/Caching/DiskCache.cs @@ -16,7 +16,6 @@ namespace ImageProcessor.Web.Caching using System.Globalization; using System.IO; using System.Linq; - using System.Threading.Tasks; using System.Web.Hosting; using ImageProcessor.Web.Configuration; @@ -130,14 +129,65 @@ namespace ImageProcessor.Web.Caching } #region Methods - #region Internal + #region Public + /// + /// Trims a cached folder ensuring that it does not exceed the maximum file count. + /// + /// + /// The path to the folder. + /// + public static void TrimCachedFolders(string path) + { + string directory = Path.GetDirectoryName(path); + + 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 parentDirectoryInfo.SafeEnumerateDirectories()) + { + IEnumerable 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 (!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. + } + } + } + } + } + } + /// /// Adds an image to the cache. /// /// /// The path to the cached image. /// - internal void AddImageToCache(string cachedPath) + public void AddImageToCache(string cachedPath) { string key = Path.GetFileNameWithoutExtension(cachedPath); CachedImage cachedImage = new CachedImage @@ -159,7 +209,7 @@ namespace ImageProcessor.Web.Caching /// /// True if the the original file is new or has been updated; otherwise, false. /// - internal bool IsNewOrUpdatedFile(string cachedPath) + public bool IsNewOrUpdatedFile(string cachedPath) { bool isUpdated = false; CachedImage cachedImage = CacheIndexer.GetValue(cachedPath); @@ -172,7 +222,7 @@ namespace ImageProcessor.Web.Caching else { // Check to see if the cached image is set to expire. - if (this.IsExpired(cachedImage.CreationTimeUtc)) + if (IsExpired(cachedImage.CreationTimeUtc)) { CacheIndexer.Remove(cachedPath); isUpdated = true; @@ -181,72 +231,22 @@ namespace ImageProcessor.Web.Caching return isUpdated; } - - /// - /// Trims a cached folder ensuring that it does not exceed the maximum file count. - /// - /// - /// The path to the folder. - /// - /// - /// The . - /// - internal async Task TrimCachedFolderAsync(string path) - { - await Task.Run(() => this.TrimCachedFolders(path)); - } #endregion #region Private /// - /// Trims a cached folder ensuring that it does not exceed the maximum file count. + /// Gets a value indicating whether the given images creation date is out with + /// the prescribed limit. /// - /// - /// The path to the folder. + /// + /// The creation date. /// - private void TrimCachedFolders(string path) + /// + /// The true if the date is out with the limit, otherwise; false. + /// + private static bool IsExpired(DateTime creationDate) { - string directory = Path.GetDirectoryName(path); - - 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 parentDirectoryInfo.SafeEnumerateDirectories()) - { - IEnumerable 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. - } - } - } - } - } + return creationDate.AddDays(MaxFileCachedDuration) < DateTime.UtcNow.AddDays(-MaxFileCachedDuration); } /// @@ -305,21 +305,6 @@ namespace ImageProcessor.Web.Caching this.virtualCachedPath = Path.Combine(VirtualCachePath, virtualPathFromKey, cachedFileName).Replace(@"\", "/"); } } - - /// - /// Gets a value indicating whether the given images creation date is out with - /// the prescribed limit. - /// - /// - /// The creation date. - /// - /// - /// The true if the date is out with the limit, otherwise; false. - /// - private bool IsExpired(DateTime creationDate) - { - return creationDate.AddDays(MaxFileCachedDuration) < DateTime.UtcNow.AddDays(-MaxFileCachedDuration); - } #endregion #endregion } diff --git a/src/ImageProcessor.Web/Helpers/RemoteFile.cs b/src/ImageProcessor.Web/Helpers/RemoteFile.cs index 6845d8453..679157dc1 100644 --- a/src/ImageProcessor.Web/Helpers/RemoteFile.cs +++ b/src/ImageProcessor.Web/Helpers/RemoteFile.cs @@ -13,12 +13,15 @@ namespace ImageProcessor.Web.Helpers #region Using using System; using System.Collections.Generic; + using System.Diagnostics; using System.Globalization; using System.IO; using System.Net; using System.Security; using System.Text; using System.Threading.Tasks; + using System.Web; + using ImageProcessor.Web.Configuration; #endregion @@ -241,27 +244,43 @@ namespace ImageProcessor.Web.Helpers /// internal async Task GetWebResponseAsync() { - WebResponse response = await this.GetWebRequest().GetResponseAsync(); + WebResponse response = null; + try + { + response = await this.GetWebRequest().GetResponseAsync(); + } + catch (WebException ex) + { + if (ex.Status == WebExceptionStatus.NameResolutionFailure) + { + throw new HttpException(404, "No image exists at " + Uri); + } - long contentLength = response.ContentLength; + throw; + } - // WebResponse.ContentLength doesn't always know the value, it returns -1 in this case. - if (contentLength == -1) + if (response != null) { - // Response headers may still have the Content-Length inside of it. - string headerContentLength = response.Headers["Content-Length"]; + long contentLength = response.ContentLength; - if (!string.IsNullOrWhiteSpace(headerContentLength)) + // WebResponse.ContentLength doesn't always know the value, it returns -1 in this case. + if (contentLength == -1) { - contentLength = long.Parse(headerContentLength, CultureInfo.InvariantCulture); + // Response headers may still have the Content-Length inside of it. + string headerContentLength = response.Headers["Content-Length"]; + + if (!string.IsNullOrWhiteSpace(headerContentLength)) + { + contentLength = long.Parse(headerContentLength, CultureInfo.InvariantCulture); + } } - } - // We don't need to check the url here since any external urls are available only from the web.config. - if ((this.MaxDownloadSize > 0) && (contentLength > this.MaxDownloadSize)) - { - response.Close(); - throw new SecurityException("An attempt to download a remote file has been halted because the file is larger than allowed."); + // We don't need to check the url here since any external urls are available only from the web.config. + if ((this.MaxDownloadSize > 0) && (contentLength > this.MaxDownloadSize)) + { + response.Close(); + throw new SecurityException("An attempt to download a remote file has been halted because the file is larger than allowed."); + } } return response; diff --git a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs index e9eeb56ab..81372f5c8 100644 --- a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs @@ -225,6 +225,9 @@ namespace ImageProcessor.Web.HttpModules { string cachedPath = cachedPathObject.ToString(); + // Trim the cache. + DiskCache.TrimCachedFolders(cachedPath); + // Fire the post processing event. EventHandler handler = OnPostProcessing; if (handler != null) @@ -347,7 +350,7 @@ namespace ImageProcessor.Web.HttpModules { string hashedUrlParameters = urlParameters.ToMD5Fingerprint(); - // TODO: Add hash for querystring parameters. + // TODO: Add hash for querystring parameters? imageName += hashedUrlParameters; fullPath += hashedUrlParameters; } @@ -425,9 +428,6 @@ namespace ImageProcessor.Web.HttpModules // Add to the cache. cache.AddImageToCache(cachedPath); - // Trim the cache. - await cache.TrimCachedFolderAsync(cachedPath); - // Store the cached path, response type, and cache dependency in the context for later retrieval. context.Items[CachedPathKey] = cachedPath; context.Items[CachedResponseTypeKey] = imageFactory.CurrentImageFormat.MimeType; @@ -440,7 +440,7 @@ namespace ImageProcessor.Web.HttpModules } else { - using (await Locker.LockAsync(cachedPath)) + using (Locker.Lock(cachedPath)) { // Check to see if the file exists. // ReSharper disable once AssignNullToNotNullAttribute @@ -459,9 +459,6 @@ namespace ImageProcessor.Web.HttpModules // Add to the cache. cache.AddImageToCache(cachedPath); - // Trim the cache. - await cache.TrimCachedFolderAsync(cachedPath); - // Store the cached path, response type, and cache dependencies in the context for later retrieval. context.Items[CachedPathKey] = cachedPath; context.Items[CachedResponseTypeKey] = imageFactory.CurrentImageFormat.MimeType; diff --git a/src/ImageProcessor/Configuration/NativeMethods.cs b/src/ImageProcessor/Configuration/NativeMethods.cs index 6c1ab9805..895de7fe5 100644 --- a/src/ImageProcessor/Configuration/NativeMethods.cs +++ b/src/ImageProcessor/Configuration/NativeMethods.cs @@ -27,7 +27,7 @@ namespace ImageProcessor.Configuration /// an executable module. /// /// If the function succeeds, the return value is a handle to the module; otherwise null. - [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] + [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)] public static extern IntPtr LoadLibrary(string libname); /// @@ -38,7 +38,7 @@ namespace ImageProcessor.Configuration /// A handle to the loaded library module. /// The LoadLibrary, LoadLibraryEx, GetModuleHandle, or GetModuleHandleEx function returns this handle. /// If the function succeeds, the return value is nonzero; otherwise zero. - [DllImport("kernel32", CharSet = CharSet.Auto)] + [DllImport("kernel32", SetLastError = true)] public static extern bool FreeLibrary(IntPtr hModule); /// diff --git a/src/TestWebsites/MVC/Web.config b/src/TestWebsites/MVC/Web.config index 3b4f02f51..e2f38f0a4 100644 --- a/src/TestWebsites/MVC/Web.config +++ b/src/TestWebsites/MVC/Web.config @@ -28,7 +28,7 @@ - +