Browse Source

Improved memory usage

Former-commit-id: 8547d9f8f2fcb4b3fda6e5f3b8373994c4ba5dda
af/merge-core
James South 12 years ago
parent
commit
db877f617a
  1. 2
      .gitignore
  2. 12
      src/ImageProcessor.Web/NET4/ImageProcessor.Web.csproj
  3. 231
      src/ImageProcessor.Web/NET45/Caching/CacheManager.cs
  4. 32
      src/ImageProcessor.Web/NET45/Caching/CleanupImage.cs
  5. 72
      src/ImageProcessor.Web/NET45/Caching/DiskCache.cs
  6. 86
      src/ImageProcessor.Web/NET45/Caching/MemoryCache.cs
  7. 48
      src/ImageProcessor.Web/NET45/Caching/SQLContext.cs
  8. 4
      src/ImageProcessor.Web/NET45/Config/ImageCacheSection.cs
  9. 321
      src/ImageProcessor.Web/NET45/Helpers/LockedDictionary.cs
  10. 11
      src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs
  11. 6
      src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj
  12. 11
      src/ImageProcessor.Web/NET45/Settings.StyleCop
  13. 1
      src/ImageProcessor/Imaging/ImageUtils.cs
  14. 11
      src/ImageProcessor/Processors/Crop.cs

2
.gitignore

@ -65,7 +65,7 @@ local.properties
**/*.dotCover
## TODO: If you have NuGet Package Restore enabled, uncomment this
**/packages/
src/packages/
# Visual C++ cache files
ipch/

12
src/ImageProcessor.Web/NET4/ImageProcessor.Web.csproj

@ -65,6 +65,7 @@
<Reference Include="System.Runtime">
<HintPath>..\..\packages\Microsoft.Bcl.1.0.19\lib\net40\System.Runtime.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Threading.Tasks">
<HintPath>..\..\packages\Microsoft.Bcl.1.0.19\lib\net40\System.Threading.Tasks.dll</HintPath>
</Reference>
@ -73,14 +74,21 @@
</ItemGroup>
<ItemGroup>
<Compile Include="..\NET45\Caching\CachedImage.cs" />
<Compile Include="..\NET45\Caching\CacheManager.cs">
<Link>CacheManager.cs</Link>
</Compile>
<Compile Include="..\NET45\Caching\CleanupImage.cs">
<Link>CleanupImage.cs</Link>
</Compile>
<Compile Include="..\NET45\Caching\DiskCache.cs" />
<Compile Include="..\NET45\Caching\PersistantDictionary.cs" />
<Compile Include="..\NET45\Caching\MemoryCache.cs">
<Link>MemoryCache.cs</Link>
</Compile>
<Compile Include="..\NET45\Caching\SQLContext.cs" />
<Compile Include="..\NET45\Config\ImageCacheSection.cs" />
<Compile Include="..\NET45\Config\ImageProcessingSection.cs" />
<Compile Include="..\NET45\Config\ImageProcessorConfig.cs" />
<Compile Include="..\NET45\Config\ImageSecuritySection.cs" />
<Compile Include="..\NET45\Helpers\LockedDictionary.cs" />
<Compile Include="..\NET45\Helpers\RemoteFile.cs" />
<Compile Include="..\NET45\Helpers\TaskHelpers.cs" />
<Compile Include="..\NET45\HttpModules\ImageProcessingModule.cs" />

231
src/ImageProcessor.Web/NET45/Caching/CacheManager.cs

@ -0,0 +1,231 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CacheManager.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encapsulates methods that allow the caching and retrieval of objects.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Web.Caching
{
#region Using
using System;
using System.Collections.Generic;
using System.Runtime.Caching;
#endregion
/// <summary>
/// Encapsulates methods that allow the caching and retrieval of objects.
/// </summary>
public static class CacheManager
{
#region Fields
/// <summary>
/// The cache
/// </summary>
private static readonly ObjectCache Cache = System.Runtime.Caching.MemoryCache.Default;
/// <summary>
/// An internal list of cache keys to allow bulk removal.
/// </summary>
private static readonly Dictionary<string, string> CacheItems = new Dictionary<string, string>();
#endregion
#region Methods
/// <summary>
/// Adds an item to the cache.
/// </summary>
/// <param name="key">
/// A unique identifier for the cache entry.
/// </param>
/// <param name="value">
/// The object to insert.
/// </param>
/// <param name="policy">
/// Optional. An <see cref="T:System.Runtime.Caching.CacheItemPolicy"/> object that contains eviction details for the cache entry. This object
/// provides more options for eviction than a simple absolute expiration. The default value for the optional parameter
/// is null.
/// </param>
/// <param name="regionName">
/// Optional. A named region in the cache to which the cache entry can be added,
/// if regions are implemented. The default value for the optional parameter
/// is null.
/// </param>
/// <returns>
/// True if the insertion try succeeds, or false if there is an already an entry
/// in the cache with the same key as key.
/// </returns>
public static bool AddItem(string key, object value, CacheItemPolicy policy = null, string regionName = null)
{
bool isAdded;
lock (Cache)
{
if (policy == null)
{
// Create a new cache policy with the default values
policy = new CacheItemPolicy();
}
isAdded = Cache.Add(key, value, policy, regionName);
if (isAdded)
{
CacheItems.Add(key, regionName);
}
}
return isAdded;
}
/// <summary>
/// Fetches an item matching the given key from the cache.
/// </summary>
/// <param name="key">
/// A unique identifier for the cache entry.
/// </param>
/// <param name="regionName">
/// Optional. A named region in the cache to which the cache entry can be added,
/// if regions are implemented. The default value for the optional parameter
/// is null.
/// </param>
/// <returns>
/// The cache entry that is identified by key.
/// </returns>
public static object GetItem(string key, string regionName = null)
{
return Cache.Get(key, regionName);
}
//public static bool
/// <summary>
/// Updates an item to the cache.
/// </summary>
/// <param name="key">
/// A unique identifier for the cache entry.
/// </param>
/// <param name="value">
/// The object to insert.
/// </param>
/// <param name="policy">
/// Optional. An <see cref="T:System.Runtime.Caching.CacheItemPolicy"/> object that contains eviction details for the cache entry. This object
/// provides more options for eviction than a simple absolute expiration. The default value for the optional parameter
/// is null.
/// </param>
/// <param name="regionName">
/// Optional. A named region in the cache to which the cache entry can be added,
/// if regions are implemented. The default value for the optional parameter
/// is null.
/// </param>
/// <returns>
/// True if the update try succeeds, or false if there is an already an entry
/// in the cache with the same key as key.
/// </returns>
public static bool UpdateItem(string key, object value, CacheItemPolicy policy = null, string regionName = null)
{
bool isUpDated = true;
// Remove the item from the cache if it already exists. MemoryCache will
// not add an item with an existing name.
if (GetItem(key, regionName) != null)
{
isUpDated = RemoveItem(key, regionName);
}
if (policy == null)
{
// Create a new cache policy with the default values
policy = new CacheItemPolicy();
}
if (isUpDated)
{
isUpDated = AddItem(key, value, policy, regionName);
}
return isUpDated;
}
/// <summary>
/// Removes an item matching the given key from the cache.
/// </summary>
/// <param name="key">
/// A unique identifier for the cache entry.
/// </param>
/// <param name="regionName">
/// Optional. A named region in the cache to which the cache entry can be added,
/// if regions are implemented. The default value for the optional parameter
/// is null.
/// </param>
/// <returns>
/// True if the removal try succeeds, or false if there is an already an entry
/// in the cache with the same key as key.
/// </returns>
public static bool RemoveItem(string key, string regionName = null)
{
bool isRemoved;
lock (Cache)
{
isRemoved = Cache.Remove(key, regionName) != null;
if (isRemoved)
{
CacheItems.Remove(key);
}
}
return isRemoved;
}
/// <summary>
/// Clears the cache.
/// </summary>
/// <param name="regionName">
/// The region name.
/// </param>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
public static bool Clear(string regionName = null)
{
bool isCleared = false;
lock (CacheItems)
{
// You can't remove items from a collection whilst you are iterating over it so you need to
// create a collection to store the items to remove.
Dictionary<string, string> tempDictionary = new Dictionary<string, string>();
foreach (KeyValuePair<string, string> cacheItem in CacheItems)
{
// Does the cached key come with a region.
if ((cacheItem.Value == null) || (cacheItem.Value != null && cacheItem.Value.Equals(regionName, StringComparison.OrdinalIgnoreCase)))
{
isCleared = RemoveItem(cacheItem.Key, cacheItem.Value);
if (isCleared)
{
tempDictionary.Add(cacheItem.Key, cacheItem.Value);
}
}
}
if (isCleared)
{
// Loop through and clear out the dictionary of cache keys.
foreach (KeyValuePair<string, string> cacheItem in tempDictionary)
{
CacheItems.Remove(cacheItem.Key);
}
}
}
return isCleared;
}
#endregion
}
}

32
src/ImageProcessor.Web/NET45/Caching/CleanupImage.cs

@ -0,0 +1,32 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CleanupImage.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Describes a cached image for cleanup.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Web.Caching
{
#region Using
using System;
#endregion
/// <summary>
/// Describes a cached image for cleanup
/// </summary>
public sealed class CleanupImage
{
/// <summary>
/// Gets or sets the value of the cached image.
/// </summary>
public string Path { get; set; }
/// <summary>
/// Gets or sets when the cached image should expire from the cache.
/// </summary>
public DateTime ExpiresUtc { get; set; }
}
}

72
src/ImageProcessor.Web/NET45/Caching/DiskCache.cs

@ -1,17 +1,18 @@
// -----------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DiskCache.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// -----------------------------------------------------------------------
// <summary>
// The disk cache.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Web.Caching
{
#region Using
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
@ -238,7 +239,7 @@ namespace ImageProcessor.Web.Caching
ExpiresUtc = expires
};
await PersistantDictionary.Instance.AddAsync(key, cachedImage);
await MemoryCache.Instance.AddAsync(key, cachedImage);
}
/// <summary>
@ -255,14 +256,16 @@ namespace ImageProcessor.Web.Caching
if (this.isRemote)
{
if (PersistantDictionary.Instance.TryGetValue(key, out cachedImage))
cachedImage = await MemoryCache.Instance.GetValueAsync(key);
if (cachedImage != null)
{
// Can't check the last write time so check to see if the cached image is set to expire
// or if the max age is different.
if (cachedImage.ExpiresUtc < DateTime.UtcNow.AddDays(-MaxFileCachedDuration)
|| cachedImage.MaxAge != MaxFileCachedDuration)
{
if (await PersistantDictionary.Instance.TryRemoveAsync(key))
if (await MemoryCache.Instance.RemoveAsync(key))
{
isUpdated = true;
}
@ -277,7 +280,9 @@ namespace ImageProcessor.Web.Caching
else
{
// Test now for locally requested files.
if (PersistantDictionary.Instance.TryGetValue(key, out cachedImage))
cachedImage = await MemoryCache.Instance.GetValueAsync(key);
if (cachedImage != null)
{
FileInfo imageFileInfo = new FileInfo(this.requestPath);
@ -289,7 +294,7 @@ namespace ImageProcessor.Web.Caching
|| cachedImage.ExpiresUtc < DateTime.UtcNow.AddDays(-MaxFileCachedDuration)
|| cachedImage.MaxAge != MaxFileCachedDuration)
{
if (await PersistantDictionary.Instance.TryRemoveAsync(key))
if (await MemoryCache.Instance.RemoveAsync(key))
{
isUpdated = true;
}
@ -314,8 +319,17 @@ namespace ImageProcessor.Web.Caching
/// </returns>
internal async Task<DateTime> GetLastWriteTimeAsync()
{
// Create Action delegate for TrimCachedFolders.
return await TaskHelpers.Run<DateTime>(this.GetLastWriteTime);
string key = Path.GetFileNameWithoutExtension(this.CachedPath);
DateTime dateTime = DateTime.UtcNow;
CachedImage cachedImage = await MemoryCache.Instance.GetValueAsync(key);
if (cachedImage != null)
{
dateTime = cachedImage.LastWriteTimeUtc;
}
return dateTime;
}
/// <summary>
@ -383,17 +397,17 @@ namespace ImageProcessor.Web.Caching
/// </summary>
private async void TrimCachedFolders()
{
// Group each cache folder and clear any expired items or any that exeed
// Group each cache folder and clear any expired items or any that exceed
// the maximum allowable count.
var groups = PersistantDictionary.Instance.ToList()
.GroupBy(x => SubFolderRegex.Match(x.Value.Path).Value)
var groups = SQLContext.GetImagesForCleanup()
.GroupBy(x => SubFolderRegex.Match(x.Path).Value)
.Where(g => g.Count() > MaxFilesCount);
foreach (var group in groups)
{
int groupCount = group.Count();
foreach (KeyValuePair<string, CachedImage> pair in group.OrderBy(x => x.Value.ExpiresUtc))
foreach (CleanupImage pair in group.OrderBy(x => x.ExpiresUtc))
{
// If the group count is equal to the max count minus 1 then we know we
// are counting down from a full directory not simply clearing out
@ -406,10 +420,10 @@ namespace ImageProcessor.Web.Caching
try
{
// Remove from the cache and delete each CachedImage.
FileInfo fileInfo = new FileInfo(pair.Value.Path);
FileInfo fileInfo = new FileInfo(pair.Path);
string key = Path.GetFileNameWithoutExtension(fileInfo.Name);
if (await PersistantDictionary.Instance.TryRemoveAsync(key))
if (await MemoryCache.Instance.RemoveAsync(key))
{
fileInfo.Delete();
groupCount -= 1;
@ -460,26 +474,6 @@ namespace ImageProcessor.Web.Caching
return cachedPath;
}
/// <summary>
/// Gets last modified time of the image.
/// </summary>
/// <returns>
/// The <see cref="DateTime"/> representing the last modified time of the image.
/// </returns>
private DateTime GetLastWriteTime()
{
string key = Path.GetFileNameWithoutExtension(this.CachedPath);
CachedImage cachedImage;
DateTime dateTime = DateTime.UtcNow;
if (PersistantDictionary.Instance.TryGetValue(key, out cachedImage))
{
dateTime = cachedImage.LastWriteTimeUtc;
}
return dateTime;
}
/// <summary>
/// The rough date time compare.
/// </summary>

86
src/ImageProcessor.Web/NET45/Caching/PersistantDictionary.cs → src/ImageProcessor.Web/NET45/Caching/MemoryCache.cs

@ -1,31 +1,32 @@
// -----------------------------------------------------------------------
// <copyright file="PersistantDictionary.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MemoryCache.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// -----------------------------------------------------------------------
// <summary>
//
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Web.Caching
{
#region Using
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ImageProcessor.Web.Helpers;
#endregion
/// <summary>
/// Represents a collection of keys and values whose operations are concurrent.
/// Represents an in memory collection of keys and values whose operations are concurrent.
/// </summary>
internal sealed class PersistantDictionary : LockedDictionary<string, CachedImage>
internal sealed class MemoryCache
{
#region Fields
/// <summary>
/// A new instance Initializes a new instance of the <see cref="T:ImageProcessor.Web.Caching.PersistantDictionary"/> class.
/// A new instance Initializes a new instance of the <see cref="T:ImageProcessor.Web.Caching.MemoryCache"/> class.
/// initialized lazily.
/// </summary>
private static readonly Lazy<PersistantDictionary> Lazy =
new Lazy<PersistantDictionary>(() => new PersistantDictionary());
private static readonly Lazy<MemoryCache> Lazy =
new Lazy<MemoryCache>(() => new MemoryCache());
/// <summary>
/// The object to lock against.
@ -35,19 +36,19 @@ namespace ImageProcessor.Web.Caching
#region Constructors
/// <summary>
/// Prevents a default instance of the <see cref="T:ImageProcessor.Web.Caching.PersistantDictionary"/> class
/// Prevents a default instance of the <see cref="T:ImageProcessor.Web.Caching.MemoryCache"/> class
/// from being created.
/// </summary>
private PersistantDictionary()
private MemoryCache()
{
this.LoadCache();
}
#endregion
/// <summary>
/// Gets the current instance of the <see cref="T:ImageProcessor.Web.Caching.PersistantDictionary"/> class.
/// Gets the current instance of the <see cref="T:ImageProcessor.Web.Caching.MemoryCache"/> class.
/// </summary>
public static PersistantDictionary Instance
public static MemoryCache Instance
{
get
{
@ -57,29 +58,47 @@ namespace ImageProcessor.Web.Caching
#region Public
/// <summary>
/// Tries to remove the value associated with the specified key.
/// Gets the <see cref="CachedImage"/> associated with the specified key.
/// </summary>
/// <param name="key">
/// The key of the item to remove.
/// The key of the value to get.
/// </param>
/// <returns>
/// true if the <see cref="PersistantDictionary"/> removes an element with
/// the specified key; otherwise, false.
/// The <see cref="CachedImage"/> matching the given key if the <see cref="MemoryCache"/> contains an element with
/// the specified key; otherwise, null.
/// </returns>
public async Task<bool> TryRemoveAsync(string key)
public async Task<CachedImage> GetValueAsync(string key)
{
// No CachedImage to remove.
if (!this.ContainsKey(key))
CachedImage cachedImage = (CachedImage)CacheManager.GetItem(key);
if (cachedImage == null)
{
return false;
cachedImage = await SQLContext.GetImageAsync(key);
if (cachedImage != null)
{
CacheManager.AddItem(key, cachedImage);
}
}
// Remove the CachedImage.
CachedImage value = this[key];
return cachedImage;
}
if (await this.SaveCacheAsync(key, value, true) > 0)
/// <summary>
/// Removes the value associated with the specified key.
/// </summary>
/// <param name="key">
/// The key of the item to remove.
/// </param>
/// <returns>
/// true if the <see cref="MemoryCache"/> removes an element with
/// the specified key; otherwise, false.
/// </returns>
public async Task<bool> RemoveAsync(string key)
{
if (await this.SaveCacheAsync(key, null, true) > 0)
{
this.Remove(key);
CacheManager.RemoveItem(key);
return true;
}
@ -103,7 +122,7 @@ namespace ImageProcessor.Web.Caching
// Add the CachedImage.
if (await this.SaveCacheAsync(key, cachedImage, false) > 0)
{
this.Add(key, cachedImage);
CacheManager.AddItem(key, cachedImage);
}
return cachedImage;
@ -131,7 +150,7 @@ namespace ImageProcessor.Web.Caching
{
if (remove)
{
return await SQLContext.RemoveImageAsync(cachedImage);
return await SQLContext.RemoveImageAsync(key);
}
return await SQLContext.AddImageAsync(cachedImage);
@ -150,13 +169,6 @@ namespace ImageProcessor.Web.Caching
lock (SyncRoot)
{
SQLContext.CreateDatabase();
Dictionary<string, CachedImage> dictionary = SQLContext.GetImages();
foreach (KeyValuePair<string, CachedImage> pair in dictionary)
{
this.Add(pair);
}
}
}
}

48
src/ImageProcessor.Web/NET45/Caching/SQLContext.cs

@ -83,29 +83,47 @@ namespace ImageProcessor.Web.Caching
/// Gets all the images from the database.
/// </summary>
/// <returns>
/// The <see cref="System.Collections.Generic.Dictionary{TKey,TVal}"/>.
/// The <see cref="System.Collections.Generic.List{CleanupImage}"/>.
/// </returns>
internal static Dictionary<string, CachedImage> GetImages()
internal static List<CleanupImage> GetImagesForCleanup()
{
Dictionary<string, CachedImage> dictionary = new Dictionary<string, CachedImage>();
try
{
List<CleanupImage> images;
using (SQLiteConnection connection = new SQLiteConnection(ConnectionString))
{
List<CachedImage> images = connection.Query<CachedImage>("SELECT * FROM CachedImage");
foreach (CachedImage cachedImage in images)
{
dictionary.Add(cachedImage.Key, cachedImage);
}
images = connection.Query<CleanupImage>("SELECT Path,ExpiresUtc FROM CachedImage");
}
return dictionary;
return images;
}
catch
{
return new List<CleanupImage>();
}
}
/// <summary>
/// Gets a cached image from the database.
/// </summary>
/// <param name="key">
/// The key for the cached image to get.
/// </param>
/// <returns>
/// The <see cref="CachedImage"/> from the database.
/// </returns>
internal static async Task<CachedImage> GetImageAsync(string key)
{
try
{
SQLiteAsyncConnection connection = new SQLiteAsyncConnection(ConnectionString);
return await connection.GetAsync<CachedImage>(c => c.Key == key);
}
catch
{
return new Dictionary<string, CachedImage>();
return null;
}
}
@ -123,7 +141,6 @@ namespace ImageProcessor.Web.Caching
try
{
SQLiteAsyncConnection connection = new SQLiteAsyncConnection(ConnectionString);
return await connection.InsertAsync(image);
}
catch
@ -135,17 +152,18 @@ namespace ImageProcessor.Web.Caching
/// <summary>
/// Removes a cached image from the database.
/// </summary>
/// <param name="cachedImage">
/// <param name="key">
/// The key for the cached image.
/// </param>
/// <returns>
/// The true if the addition of the cached image is removed; otherwise, false.
/// </returns>
internal static async Task<int> RemoveImageAsync(CachedImage cachedImage)
internal static async Task<int> RemoveImageAsync(string key)
{
try
{
SQLiteAsyncConnection connection = new SQLiteAsyncConnection(ConnectionString);
CachedImage cachedImage = await connection.GetAsync<CachedImage>(c => c.Key == key);
return await connection.DeleteAsync(cachedImage);
}

4
src/ImageProcessor.Web/NET45/Config/ImageCacheSection.cs

@ -21,7 +21,7 @@ namespace ImageProcessor.Web.Config
/// Gets or sets the virtual path of the cache folder.
/// </summary>
/// <value>The name of the cache folder.</value>
[ConfigurationProperty("virtualPath", DefaultValue = "~/cache", IsRequired = true)]
[ConfigurationProperty("virtualPath", DefaultValue = "~/app_data/cache", IsRequired = true)]
[StringValidator(MinLength = 3, MaxLength = 200)]
public string VirtualPath
{
@ -29,7 +29,7 @@ namespace ImageProcessor.Web.Config
{
string virtualPath = (string)this["virtualPath"];
return virtualPath.IsValidVirtualPathName() ? virtualPath : "~/cache";
return virtualPath.IsValidVirtualPathName() ? virtualPath : "~/app_data/cache";
}
set

321
src/ImageProcessor.Web/NET45/Helpers/LockedDictionary.cs

@ -1,321 +0,0 @@
// -----------------------------------------------------------------------
// <copyright file="LockedDictionary.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// -----------------------------------------------------------------------
namespace ImageProcessor.Web.Helpers
{
#region Using
using System.Collections.Generic;
using System.Linq;
#endregion
/// <summary>
/// Represents a collection of keys and values that are thread safe.
/// </summary>
/// <typeparam name="TKey">
/// The type of the keys in the dictionary.
/// </typeparam>
/// <typeparam name="TVal">
/// The type of the values in the dictionary.
/// </typeparam>
internal class LockedDictionary<TKey, TVal> : IDictionary<TKey, TVal>
{
/// <summary>
/// The _inner.
/// </summary>
private readonly Dictionary<TKey, TVal> innerDictionary = new Dictionary<TKey, TVal>();
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="LockedDictionary{TKey,TVal}"/> class.
/// </summary>
/// <param name="val">
/// The value to initialize the LockedDictionary with.
/// </param>
public LockedDictionary(IEnumerable<KeyValuePair<TKey, TVal>> val = null)
{
if (val != null)
{
this.innerDictionary = val.ToDictionary(x => x.Key, x => x.Value);
}
}
#endregion
#region Properties
/// <summary>
/// Gets a collection containing the keys in the <see cref="LockedDictionary{TKey,TVal}"/>.
/// </summary>
public ICollection<TKey> Keys
{
get
{
lock (this.innerDictionary)
{
return this.innerDictionary.Keys.ToArray();
}
}
}
/// <summary>
/// Gets a collection containing the values in the <see cref="LockedDictionary{TKey,TVal}"/>.
/// </summary>
public ICollection<TVal> Values
{
get
{
lock (this.innerDictionary)
{
return this.innerDictionary.Values.ToArray();
}
}
}
/// <summary>
/// Gets the number of key/value pairs contained in the <see cref="LockedDictionary{TKey,TVal}"/>.
/// </summary>
public int Count
{
get
{
lock (this.innerDictionary)
{
return this.innerDictionary.Count;
}
}
}
/// <summary>
/// Gets a value indicating whether the <see cref="LockedDictionary{TKey,TVal}"/> is read only.
/// </summary>
public bool IsReadOnly
{
get { return false; }
}
/// <summary>
/// Gets or sets the value associated with the specified key.
/// </summary>
/// <param name="key">
/// The key of the value to get or set.
/// </param>
/// <returns>
/// TThe value associated with the specified key. If the specified key is not found,
/// a get operation throws a <see cref="T:System.Collections.Generic.KeyNotFoundException"/> ,
/// and a set operation creates a new element with the specified key.
/// </returns>
public TVal this[TKey key]
{
get
{
lock (this.innerDictionary)
{
return this.innerDictionary[key];
}
}
set
{
lock (this.innerDictionary)
{
this.innerDictionary[key] = value;
}
}
}
#endregion
#region Methods
/// <summary>
/// Adds the specified key and value to the dictionary.
/// </summary>
/// <param name="key">
/// The key of the element to add.
/// </param>
/// <param name="value">
/// The value of the element to add. The value can be null for reference types.
/// </param>
public void Add(TKey key, TVal value)
{
lock (this.innerDictionary)
{
this.innerDictionary.Add(key, value);
}
}
/// <summary>
/// Determines whether the LockedDictionary contains the specified key.
/// </summary>
/// <param name="key">
/// The key to locate in the LockedDictionary.
/// </param>
/// <returns>
/// true if the LockedDictionary contains the key; otherwise, false.
/// </returns>
public bool ContainsKey(TKey key)
{
lock (this.innerDictionary)
{
return this.innerDictionary.ContainsKey(key);
}
}
/// <summary>
/// Removes the value with the specified key from the <see cref="LockedDictionary{TKey,TVal}"/>.
/// </summary>
/// <param name="key">
/// The key of the element to remove.
/// </param>
/// <returns>
/// true if the element is successfully found and removed; otherwise, false.
/// This method returns false if key is not found in the <see cref="LockedDictionary{TKey,TVal}"/>.
/// </returns>
public bool Remove(TKey key)
{
lock (this.innerDictionary)
{
return this.innerDictionary.Remove(key);
}
}
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">
/// The key of the value to get.
/// </param>
/// <param name="value">
/// When this method returns, contains the value associated with the specified key, if the key is found;
/// otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized.
/// </param>
/// <returns>
/// true if the <see cref="LockedDictionary{TKey,TVal}"/> contains an element with
/// the specified key; otherwise, false.
/// </returns>
public bool TryGetValue(TKey key, out TVal value)
{
lock (this.innerDictionary)
{
return this.innerDictionary.TryGetValue(key, out value);
}
}
/// <summary>
/// Adds the specified key and value to the dictionary.
/// </summary>
/// <param name="item">
/// The <see cref="System.Collections.Generic.KeyValuePair{TKey, TVal}"/> representing
/// the item to add.
/// </param>
public void Add(KeyValuePair<TKey, TVal> item)
{
lock (this.innerDictionary)
{
this.innerDictionary.Add(item.Key, item.Value);
}
}
/// <summary>
/// Removes all keys and values from the <see cref="LockedDictionary{TKey,TVal}"/>.
/// </summary>
public void Clear()
{
lock (this.innerDictionary)
{
this.innerDictionary.Clear();
}
}
/// <summary>
/// Determines whether the <see cref="LockedDictionary{TKey,TVal}"/> contains the specified key.
/// </summary>
/// <param name="item">
/// The <see cref="System.Collections.Generic.KeyValuePair{TKey, TVal}"/> representing
/// the item to locate.
/// </param>
/// <returns>
/// true if the <see cref="LockedDictionary{TKey,TVal}"/> contains an element
/// with the specified key; otherwise, false.
/// </returns>
public bool Contains(KeyValuePair<TKey, TVal> item)
{
lock (this.innerDictionary)
{
var inner = this.innerDictionary as IDictionary<TKey, TVal>;
return inner.Contains(item);
}
}
/// <summary>
/// Copies the elements of an <see cref="LockedDictionary{TKey,TVal}"/> to a one-dimensional
/// <see cref="T:System.Array"/> starting at a particular <see cref="T:System.Array"/> index.
/// </summary>
/// <param name="array">
/// The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied
/// from <see cref="LockedDictionary{TKey,TVal}"/>.KeyCollection.
/// The <see cref="T:System.Array"/> must have zero-based indexing.
/// </param>
/// <param name="arrayIndex">
/// The zero-based index in array at which copying begins.
/// </param>
public void CopyTo(KeyValuePair<TKey, TVal>[] array, int arrayIndex)
{
lock (this.innerDictionary)
{
var inner = this.innerDictionary as IDictionary<TKey, TVal>;
inner.CopyTo(array, arrayIndex);
}
}
/// <summary>
/// Removes the item with the specified <see cref="System.Collections.Generic.KeyValuePair{TKey, TVal}"/>
/// from the <see cref="LockedDictionary{TKey,TVal}"/>
/// </summary>
/// <param name="item">
/// The <see cref="System.Collections.Generic.KeyValuePair{TKey, TVal}"/>representing the item to remove.
/// </param>
/// <returns>
/// This method returns false if item is not found in the <see cref="LockedDictionary{TKey,TVal}"/>.
/// </returns>
public bool Remove(KeyValuePair<TKey, TVal> item)
{
lock (this.innerDictionary)
{
var inner = this.innerDictionary as IDictionary<TKey, TVal>;
return inner.Remove(item);
}
}
/// <summary>
/// Returns an enumerator that iterates through the <see cref="LockedDictionary{TKey,TVal}"/>.KeyCollection.
/// </summary>
/// <returns>
/// A <see cref="System.Collections.Generic.Dictionary{TKey,TValue}.KeyCollection.Enumerator"/>
/// for the <see cref="System.Collections.Generic.Dictionary{TKey,TValue}.KeyCollection"/>.
/// </returns>
public IEnumerator<KeyValuePair<TKey, TVal>> GetEnumerator()
{
lock (this.innerDictionary)
{
return this.innerDictionary.ToList().GetEnumerator();
}
}
/// <summary>
/// Returns an enumerator that iterates through the <see cref="LockedDictionary{TKey,TVal}"/>.KeyCollection.
/// </summary>
/// <returns>
/// A <see cref="System.Collections.Generic.Dictionary{TKey,TValue}.Enumerator"/>
/// for the <see cref="System.Collections.Generic.Dictionary{TKey,TValue}"/>.
/// </returns>
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
lock (this.innerDictionary)
{
return this.innerDictionary.ToArray().GetEnumerator();
}
}
#endregion
}
}

11
src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs

@ -1,9 +1,12 @@
// -----------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ImageProcessingModule.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// -----------------------------------------------------------------------
// <summary>
// Processes any image requests within the web application.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Web.HttpModules
{

6
src/ImageProcessor.Web/NET45/ImageProcessor.Web_NET45.csproj

@ -40,6 +40,7 @@
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Web" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
@ -49,14 +50,15 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Caching\CachedImage.cs" />
<Compile Include="Caching\CacheManager.cs" />
<Compile Include="Caching\CleanupImage.cs" />
<Compile Include="Caching\DiskCache.cs" />
<Compile Include="Caching\PersistantDictionary.cs" />
<Compile Include="Caching\MemoryCache.cs" />
<Compile Include="Caching\SQLContext.cs" />
<Compile Include="Config\ImageCacheSection.cs" />
<Compile Include="Config\ImageProcessingSection.cs" />
<Compile Include="Config\ImageProcessorConfig.cs" />
<Compile Include="Config\ImageSecuritySection.cs" />
<Compile Include="Helpers\LockedDictionary.cs" />
<Compile Include="Helpers\RemoteFile.cs" />
<Compile Include="Helpers\TaskHelpers.cs" />
<Compile Include="HttpModules\ImageProcessingModule.cs" />

11
src/ImageProcessor.Web/NET45/Settings.StyleCop

@ -0,0 +1,11 @@
<StyleCopSettings Version="105">
<Analyzers>
<Analyzer AnalyzerId="StyleCop.CSharp.DocumentationRules">
<AnalyzerSettings>
<StringProperty Name="CompanyName">James South</StringProperty>
<StringProperty Name="Copyright">Copyright (c) James South.
Licensed under the Apache License, Version 2.0.</StringProperty>
</AnalyzerSettings>
</Analyzer>
</Analyzers>
</StyleCopSettings>

1
src/ImageProcessor/Imaging/ImageUtils.cs

@ -210,7 +210,6 @@ namespace ImageProcessor.Imaging
/// <returns>True the value contains a valid image extension, otherwise false.</returns>
public static bool IsValidImageExtension(string fileName)
{
return FormatRegex.IsMatch(fileName);
}

11
src/ImageProcessor/Processors/Crop.cs

@ -1,9 +1,12 @@
// -----------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Crop.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// -----------------------------------------------------------------------
// <summary>
// Crops an image to the given directions.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Processors
{

Loading…
Cancel
Save