diff --git a/src/ImageProcessor.Web/Caching/CachedImage.cs b/src/ImageProcessor.Web/Caching/CachedImage.cs new file mode 100644 index 000000000..500331d55 --- /dev/null +++ b/src/ImageProcessor.Web/Caching/CachedImage.cs @@ -0,0 +1,66 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) James South. +// Dual licensed under the MIT or GPL Version 2 licenses. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Web.Caching +{ + #region Using + using System; + #endregion + + /// + /// Describes a cached image + /// + public class CachedImage + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The value of the cached item. + /// + /// + /// The last write time of the cached item. + /// + public CachedImage(string value, DateTime lastWriteTimeUtc) + { + this.Value = value; + this.LastWriteTimeUtc = lastWriteTimeUtc; + } + + /// + /// Gets the value date time delimiter. + /// + public static string ValueLastWriteTimeDelimiter + { + get + { + return "|*|"; + } + } + + /// + /// Gets or sets the value of the cached item + /// + public string Value { get; set; } + + /// + /// Gets or sets the last write time of the cached item + /// + public DateTime LastWriteTimeUtc { get; set; } + + /// + /// The value and last write time as a string. + /// + /// + /// The . + /// + public string ValueAndLastWriteTimeUtcToString() + { + return string.Format("{0}{1}{2}", this.Value, ValueLastWriteTimeDelimiter, this.LastWriteTimeUtc); + } + } +} diff --git a/src/ImageProcessor.Web/Caching/DiskCache.cs b/src/ImageProcessor.Web/Caching/DiskCache.cs index e4abdee97..b5f828825 100644 --- a/src/ImageProcessor.Web/Caching/DiskCache.cs +++ b/src/ImageProcessor.Web/Caching/DiskCache.cs @@ -131,7 +131,7 @@ namespace ImageProcessor.Web.Caching } /// - /// Purges any files from the filesystem cache in a background thread. + /// Purges any files from the file-system cache in a background thread. /// internal static void PurgeCachedFolders() { @@ -184,7 +184,7 @@ namespace ImageProcessor.Web.Caching } /// - /// Purges any files from the filesystem cache in the given folders. + /// Purges any files from the file-system cache in the given folders. /// private static void PurgeFolders() { @@ -231,7 +231,6 @@ namespace ImageProcessor.Web.Caching } } } - }); }); } diff --git a/src/ImageProcessor.Web/Caching/LockedDictionary.cs b/src/ImageProcessor.Web/Caching/LockedDictionary.cs new file mode 100644 index 000000000..de8e214a3 --- /dev/null +++ b/src/ImageProcessor.Web/Caching/LockedDictionary.cs @@ -0,0 +1,321 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) James South. +// Dual licensed under the MIT or GPL Version 2 licenses. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Web.Caching +{ + #region Using + using System.Collections.Generic; + using System.Linq; + #endregion + + /// + /// Represents a collection of keys and values that are thread safe. + /// + /// + /// The type of the keys in the dictionary. + /// + /// + /// The type of the values in the dictionary. + /// + public class LockedDictionary : IDictionary + { + /// + /// The _inner. + /// + private readonly Dictionary innerDictionary = new Dictionary(); + + #region Constructors + /// + /// Initializes a new instance of the class. + /// + /// + /// The value to initialize the LockedDictionary with. + /// + public LockedDictionary(IDictionary val = null) + { + if (val != null) + { + this.innerDictionary = val.ToDictionary(x => x.Key, x => x.Value); + } + } + #endregion + + #region Properties + /// + /// Gets a collection containing the keys in the . + /// + public ICollection Keys + { + get + { + lock (this.innerDictionary) + { + return this.innerDictionary.Keys.ToArray(); + } + } + } + + /// + /// Gets a collection containing the values in the . + /// + public ICollection Values + { + get + { + lock (this.innerDictionary) + { + return this.innerDictionary.Values.ToArray(); + } + } + } + + /// + /// Gets the number of key/value pairs contained in the . + /// + public int Count + { + get + { + lock (this.innerDictionary) + { + return this.innerDictionary.Count; + } + } + } + + /// + /// Gets a value indicating whether the is read only. + /// + public bool IsReadOnly + { + get { return false; } + } + + /// + /// Gets or sets the value associated with the specified key. + /// + /// + /// The key of the value to get or set. + /// + /// + /// TThe value associated with the specified key. If the specified key is not found, + /// a get operation throws a , + /// and a set operation creates a new element with the specified key. + /// + public TVal this[TKey key] + { + get + { + lock (this.innerDictionary) + { + return this.innerDictionary[key]; + } + } + + set + { + lock (this.innerDictionary) + { + this.innerDictionary[key] = value; + } + } + } + #endregion + + #region Methods + /// + /// Adds the specified key and value to the dictionary. + /// + /// + /// The key of the element to add. + /// + /// + /// The value of the element to add. The value can be null for reference types. + /// + public void Add(TKey key, TVal value) + { + lock (this.innerDictionary) + { + this.innerDictionary.Add(key, value); + } + } + + /// + /// Determines whether the LockedDictionary contains the specified key. + /// + /// + /// The key to locate in the LockedDictionary. + /// + /// + /// true if the LockedDictionary contains the key; otherwise, false. + /// + public bool ContainsKey(TKey key) + { + lock (this.innerDictionary) + { + return this.innerDictionary.ContainsKey(key); + } + } + + /// + /// Removes the value with the specified key from the . + /// + /// + /// The key of the element to remove. + /// + /// + /// true if the element is successfully found and removed; otherwise, false. + /// This method returns false if key is not found in the . + /// + public bool Remove(TKey key) + { + lock (this.innerDictionary) + { + return this.innerDictionary.Remove(key); + } + } + + /// + /// Gets the value associated with the specified key. + /// + /// + /// The key of the value to get. + /// + /// + /// 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. + /// + /// + /// true if the contains an element with + /// the specified key; otherwise, false. + /// + public bool TryGetValue(TKey key, out TVal value) + { + lock (this.innerDictionary) + { + return this.innerDictionary.TryGetValue(key, out value); + } + } + + /// + /// Adds the specified key and value to the dictionary. + /// + /// + /// The representing + /// the item to add. + /// + public void Add(KeyValuePair item) + { + lock (this.innerDictionary) + { + this.innerDictionary.Add(item.Key, item.Value); + } + } + + /// + /// Removes all keys and values from the . + /// + public void Clear() + { + lock (this.innerDictionary) + { + this.innerDictionary.Clear(); + } + } + + /// + /// Determines whether the contains the specified key. + /// + /// + /// The representing + /// the item to locate. + /// + /// + /// true if the contains an element + /// with the specified key; otherwise, false. + /// + public bool Contains(KeyValuePair item) + { + lock (this.innerDictionary) + { + var inner = this.innerDictionary as IDictionary; + return inner.Contains(item); + } + } + + /// + /// Copies the elements of an to a one-dimensional + /// starting at a particular index. + /// + /// + /// The one-dimensional that is the destination of the elements copied + /// from .KeyCollection. + /// The must have zero-based indexing. + /// + /// + /// The zero-based index in array at which copying begins. + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + lock (this.innerDictionary) + { + var inner = this.innerDictionary as IDictionary; + inner.CopyTo(array, arrayIndex); + } + } + + /// + /// Removes the item with the specified + /// from the + /// + /// + /// The representing the item to remove. + /// + /// + /// This method returns false if item is not found in the . + /// + public bool Remove(KeyValuePair item) + { + lock (this.innerDictionary) + { + var inner = this.innerDictionary as IDictionary; + return inner.Remove(item); + } + } + + /// + /// Returns an enumerator that iterates through the .KeyCollection. + /// + /// + /// A + /// for the . + /// + public IEnumerator> GetEnumerator() + { + lock (this.innerDictionary) + { + return this.innerDictionary.ToList().GetEnumerator(); + } + } + + /// + /// Returns an enumerator that iterates through the .KeyCollection. + /// + /// + /// A + /// for the . + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + lock (this.innerDictionary) + { + return this.innerDictionary.ToArray().GetEnumerator(); + } + } + #endregion + } +} \ No newline at end of file diff --git a/src/ImageProcessor.Web/Caching/PersistantDictionary.cs b/src/ImageProcessor.Web/Caching/PersistantDictionary.cs new file mode 100644 index 000000000..4f0f1f134 --- /dev/null +++ b/src/ImageProcessor.Web/Caching/PersistantDictionary.cs @@ -0,0 +1,193 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) James South. +// Dual licensed under the MIT or GPL Version 2 licenses. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Web.Caching +{ + #region Using + using System; + using System.IO; + using System.Web.Hosting; + using ImageProcessor.Web.Config; + #endregion + + /// + /// Represents a collection of keys and values whose operations are concurrent. + /// + public class PersistantDictionary : LockedDictionary + { + /// + /// A new instance Initializes a new instance of the class. + /// initialized lazily. + /// + private static readonly Lazy Lazy = + new Lazy(() => new PersistantDictionary()); + + /// + /// The default path for cached folders on the server. + /// + private static readonly string CachePath = ImageProcessorConfig.Instance.VirtualCachePath; + + /// + /// The object to lock against. + /// + private static readonly object SyncRoot = new object(); + + /// + /// The cached index location. + /// + private readonly string cachedIndexFile = Path.Combine(HostingEnvironment.MapPath(CachePath), "imagecache.bin"); + + #region Constructors + /// + /// Prevents a default instance of the class + /// from being created. + /// + private PersistantDictionary() + { + this.LoadCache(); + } + #endregion + + /// + /// Gets the current instance of the class. + /// + public static PersistantDictionary Instance + { + get + { + return Lazy.Value; + } + } + + #region Public + /// + /// Tries to remove the value associated with the specified key. + /// + /// + /// The key of the item to remove. + /// + /// + /// The value to assign the returned value to. + /// + /// + /// true if the removes an element with + /// the specified key; otherwise, false. + /// + public bool TryRemove(string key, out CachedImage value) + { + // No CachedImage to remove. + if (!this.ContainsKey(key)) + { + value = default(CachedImage); + return false; + } + + // Remove the CachedImage. + lock (SyncRoot) + { + value = this[key]; + this.Remove(key); + + this.SaveCache(); + + return true; + } + } + + /// + /// Adds the specified key and value to the dictionary or returns the value if it exists. + /// + /// + /// The key. + /// + /// + /// The delegate method that returns the value. + /// + /// + /// The value of the item to add or get. + /// + public CachedImage GetOrAdd(string key, Func factory) + { + // Get the CachedImage. + if (this.ContainsKey(key)) + { + return this[key]; + } + + lock (SyncRoot) + { + // Add the CachedImage. + CachedImage ret = factory(key); + this[key] = ret; + + this.SaveCache(); + + return ret; + } + } + #endregion + + /// + /// Saves the in memory cache to the file-system. + /// + private void SaveCache() + { + using (FileStream fileStream = File.Create(this.cachedIndexFile)) + { + using (BinaryWriter binaryWriter = new BinaryWriter(fileStream)) + { + // Put the count. + binaryWriter.Write(this.Count); + + // Put the values. + foreach (var pair in this) + { + binaryWriter.Write(pair.Key); + binaryWriter.Write(pair.Value.ValueAndLastWriteTimeUtcToString()); + } + } + } + } + + /// + /// Loads the cache file to populate the in memory cache. + /// + private void LoadCache() + { + lock (SyncRoot) + { + if (File.Exists(this.cachedIndexFile)) + { + using (FileStream fileStream = File.OpenRead(this.cachedIndexFile)) + { + using (BinaryReader binaryReader = new BinaryReader(fileStream)) + { + // Get the count. + int count = binaryReader.ReadInt32(); + + // Read in all pairs. + for (int i = 0; i < count; i++) + { + // Read the key/value strings + string key = binaryReader.ReadString(); + string value = binaryReader.ReadString(); + + // Create a CachedImage + string[] valueAndLastWriteTime = value.Split(new[] { CachedImage.ValueLastWriteTimeDelimiter }, StringSplitOptions.None); + DateTime lastWriteTime = DateTime.Parse(valueAndLastWriteTime[1]); + CachedImage cachedImage = new CachedImage(valueAndLastWriteTime[0], lastWriteTime); + + // Assign the value + this[key] = cachedImage; + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor.Web/Config/ImageProcessorConfig.cs b/src/ImageProcessor.Web/Config/ImageProcessorConfig.cs index 1c35ff9bc..7332b2d1b 100644 --- a/src/ImageProcessor.Web/Config/ImageProcessorConfig.cs +++ b/src/ImageProcessor.Web/Config/ImageProcessorConfig.cs @@ -10,14 +10,16 @@ namespace ImageProcessor.Web.Config #region Using using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Linq; using ImageProcessor.Processors; #endregion /// - /// Encapsulates methods to allow the retrieval of imageprocessor settings. + /// Encapsulates methods to allow the retrieval of ImageProcessor settings. /// http://csharpindepth.com/Articles/General/Singleton.aspx /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] public class ImageProcessorConfig { #region Fields @@ -107,6 +109,7 @@ namespace ImageProcessor.Web.Config /// /// Gets a list of whitelisted urls that images can be downloaded from. /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] public Uri[] RemoteFileWhiteList { get diff --git a/src/ImageProcessor.Web/Helpers/FileCompareLastwritetime.cs b/src/ImageProcessor.Web/Helpers/FileCompareLastwritetime.cs index 3aa938b8f..a99eb520d 100644 --- a/src/ImageProcessor.Web/Helpers/FileCompareLastwritetime.cs +++ b/src/ImageProcessor.Web/Helpers/FileCompareLastwritetime.cs @@ -49,7 +49,7 @@ namespace ImageProcessor.Web.Helpers /// /// Returns a hash code for the specified . /// - /// The FileInfo to return the hashcode for. + /// The FileInfo to return the hash code for. /// A hash code for the specified . public int GetHashCode(System.IO.FileInfo fi) { diff --git a/src/ImageProcessor.Web/ImageFactoryExtensions.cs b/src/ImageProcessor.Web/ImageFactoryExtensions.cs index 2feff79fd..1b05bb669 100644 --- a/src/ImageProcessor.Web/ImageFactoryExtensions.cs +++ b/src/ImageProcessor.Web/ImageFactoryExtensions.cs @@ -15,14 +15,17 @@ namespace ImageProcessor.Web #endregion /// - /// Extends the ImageFactory class to provide a fluent api. + /// Extends the ImageFactory class to provide a fluent API. /// public static class ImageFactoryExtensions { + /// + /// The object to lock against. + /// private static readonly object SyncRoot = new object(); /// - /// Auto processes image files based on any querystring parameters added to the image path. + /// Auto processes image files based on any query string parameters added to the image path. /// /// /// The current instance of the class @@ -31,32 +34,29 @@ namespace ImageProcessor.Web /// /// The current instance of the class. /// -public static ImageFactory AutoProcess(this ImageFactory factory) -{ - if (factory.ShouldProcess) - { - // TODO: This is going to be a bottleneck for speed. Find a faster way. - lock (SyncRoot) + public static ImageFactory AutoProcess(this ImageFactory factory) { - // Get a list of all graphics processors that have parsed and matched the querystring. - List list = - ImageProcessorConfig.Instance.GraphicsProcessors - .Where(x => x.MatchRegexIndex(factory.QueryString) != int.MaxValue) - .OrderBy(y => y.SortOrder) - .ToList(); - - // Loop through and process the image. - foreach (IGraphicsProcessor graphicsProcessor in list) + if (factory.ShouldProcess) { - factory.Image = graphicsProcessor.ProcessImage(factory); + // TODO: This is going to be a bottleneck for speed. Find a faster way. + lock (SyncRoot) + { + // Get a list of all graphics processors that have parsed and matched the querystring. + List list = + ImageProcessorConfig.Instance.GraphicsProcessors + .Where(x => x.MatchRegexIndex(factory.QueryString) != int.MaxValue) + .OrderBy(y => y.SortOrder) + .ToList(); + + // Loop through and process the image. + foreach (IGraphicsProcessor graphicsProcessor in list) + { + factory.Image = graphicsProcessor.ProcessImage(factory); + } + } } - } - } - - return factory; - -} - + return factory; + } } } diff --git a/src/ImageProcessor.Web/ImageProcessor.Web.csproj b/src/ImageProcessor.Web/ImageProcessor.Web.csproj index 6f5589cf3..0ef489007 100644 --- a/src/ImageProcessor.Web/ImageProcessor.Web.csproj +++ b/src/ImageProcessor.Web/ImageProcessor.Web.csproj @@ -51,7 +51,10 @@ + + + diff --git a/src/Test/Test/Content/style.css b/src/Test/Test/Content/style.css index 6848b86e7..b94de3ac8 100644 --- a/src/Test/Test/Content/style.css +++ b/src/Test/Test/Content/style.css @@ -19,25 +19,14 @@ section section { } section section:nth-child(2n) { - color: #fff; - } - - - section section:nth-child(2) { background-color: #00a9ec; + color: #fff; } - section section:nth-child(4) { + section section:nth-child(4n) { background-color: #ED1C24; } -img { - /*background: #fff; - padding: 3px; - -webkit-box-shadow: 0 0 5px rgb(0, 0, 0, 0.4); - box-shadow: 0 0 5px rgb(0, 0, 0, 0.4);*/ -} - .no-bullets { padding-left: 0; } diff --git a/src/Test/Test/Images/Penguins.bmp.REMOVED.git-id b/src/Test/Test/Images/Penguins.bmp.REMOVED.git-id new file mode 100644 index 000000000..74f69293c --- /dev/null +++ b/src/Test/Test/Images/Penguins.bmp.REMOVED.git-id @@ -0,0 +1 @@ +8150b46ab27c62ba51aaba551eef3f1a30f08de9 \ No newline at end of file diff --git a/src/Test/Test/Images/Penguins.gif.REMOVED.git-id b/src/Test/Test/Images/Penguins.gif.REMOVED.git-id new file mode 100644 index 000000000..ce873d473 --- /dev/null +++ b/src/Test/Test/Images/Penguins.gif.REMOVED.git-id @@ -0,0 +1 @@ +6ad3b846d4697584ff601ac481b14a4d3bbb5736 \ No newline at end of file diff --git a/src/Test/Test/Images/Penguins.png.REMOVED.git-id b/src/Test/Test/Images/Penguins.png.REMOVED.git-id new file mode 100644 index 000000000..78062a0e7 --- /dev/null +++ b/src/Test/Test/Images/Penguins.png.REMOVED.git-id @@ -0,0 +1 @@ +a2c796fbb7de948230a22982ab74892891dd5198 \ No newline at end of file diff --git a/src/Test/Test/Test.csproj b/src/Test/Test/Test.csproj index b59d56d3d..7198d738b 100644 --- a/src/Test/Test/Test.csproj +++ b/src/Test/Test/Test.csproj @@ -89,8 +89,12 @@ + + + + diff --git a/src/Test/Test/Views/Home/Index.cshtml b/src/Test/Test/Views/Home/Index.cshtml index 981fb6aaf..05b7a8885 100644 --- a/src/Test/Test/Views/Home/Index.cshtml +++ b/src/Test/Test/Views/Home/Index.cshtml @@ -1,94 +1,367 @@ @{ ViewBag.Title = "Home Page"; } -
-
-
-

Resized

- -
-
-

Cropped

- -
-
-
-
-
-

Filter

-
    -
  • -

    blackwhite

    - -
  • -
  • -

    comic

    - -
  • -
  • -

    lomograph

    - -
  • -
  • -

    greyscale

    - -
  • -
  • -

    polaroid

    - -
  • -
  • -

    sepia

    - -
  • -
  • -

    gotham

    - -
  • -
  • -

    hisatch

    - -
  • -
  • -

    losatch

    - -
  • -
-
-
-
-
-
-

Watermark

- -
-
-

Format

- -
-
-
-
-
-
-

Rotate

- -
-
-

Quality

- -
-
-
-
-
-
-

Alpha

- -
-
-

Remote

- -
-
-
+
+

Jpg

+
+
+
+

Resized

+ +
+
+

Cropped

+ +
+
+
+
+
+

Filter

+
    +
  • +

    blackwhite

    + +
  • +
  • +

    comic

    + +
  • +
  • +

    lomograph

    + +
  • +
  • +

    greyscale

    + +
  • +
  • +

    polaroid

    + +
  • +
  • +

    sepia

    + +
  • +
  • +

    gotham

    + +
  • +
  • +

    hisatch

    + +
  • +
  • +

    losatch

    + +
  • +
+
+
+
+
+
+

Watermark

+ +
+
+

Format

+ +
+
+
+
+
+
+

Rotate

+ +
+
+

Quality

+ +
+
+
+
+
+
+

Alpha

+ +
+
+

Remote

+ +
+
+
+
+
+

Gif

+
+
+
+

Resized

+ +
+
+

Cropped

+ +
+
+
+
+
+

Filter

+
    +
  • +

    blackwhite

    + +
  • +
  • +

    comic

    + +
  • +
  • +

    lomograph

    + +
  • +
  • +

    greyscale

    + +
  • +
  • +

    polaroid

    + +
  • +
  • +

    sepia

    + +
  • +
  • +

    gotham

    + +
  • +
  • +

    hisatch

    + +
  • +
  • +

    losatch

    + +
  • +
+
+
+
+
+
+

Watermark

+ +
+
+

Format

+ +
+
+
+
+
+
+

Rotate

+ +
+
+

Quality

+ +
+
+
+
+
+
+

Alpha

+ +
+
+
+
+
+

Png

+
+
+
+

Resized

+ +
+
+

Cropped

+ +
+
+
+
+
+

Filter

+
    +
  • +

    blackwhite

    + +
  • +
  • +

    comic

    + +
  • +
  • +

    lomograph

    + +
  • +
  • +

    greyscale

    + +
  • +
  • +

    polaroid

    + +
  • +
  • +

    sepia

    + +
  • +
  • +

    gotham

    + +
  • +
  • +

    hisatch

    + +
  • +
  • +

    losatch

    + +
  • +
+
+
+
+
+
+

Watermark

+ +
+
+

Format

+ +
+
+
+
+
+
+

Rotate

+ +
+
+

Quality

+ +
+
+
+
+
+
+

Alpha

+ +
+
+
+
+
+

Bmp

+
+
+
+

Resized

+ +
+
+

Cropped

+ +
+
+
+
+
+

Filter

+
    +
  • +

    blackwhite

    + +
  • +
  • +

    comic

    + +
  • +
  • +

    lomograph

    + +
  • +
  • +

    greyscale

    + +
  • +
  • +

    polaroid

    + +
  • +
  • +

    sepia

    + +
  • +
  • +

    gotham

    + +
  • +
  • +

    hisatch

    + +
  • +
  • +

    losatch

    + +
  • +
+
+
+
+
+
+

Watermark

+ +
+
+

Format

+ +
+
+
+
+
+
+

Rotate

+ +
+
+

Quality

+ +
+
+
+
+
+
+

Alpha

+ +
+
+
+