From 739a6c47a45e7f8a1cdeac117190cf06d1d3c2b3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 23 Jul 2013 17:49:54 +1000 Subject: [PATCH] Obsoletes UmbracoFile with new UmbracoMediaFile in the Core assembly and wraps the legacy class to use the new one. Improves performance of UmbracoMediaFile by ensuring any IO is lazy loaded and then saved to a local property so it doesn't need to re-read the file for things like length/size. --- src/Umbraco.Core/IO/ResizedImage.cs | 20 ++ src/Umbraco.Core/IO/UmbracoMediaFile.cs | 253 ++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 2 + src/umbraco.cms/businesslogic/Files/IFile.cs | 1 + .../Files/NotAnImageException.cs | 1 + .../businesslogic/Files/UmbracoFile.cs | 202 +++----------- .../businesslogic/datatype/FileHandlerData.cs | 8 +- 7 files changed, 321 insertions(+), 166 deletions(-) create mode 100644 src/Umbraco.Core/IO/ResizedImage.cs create mode 100644 src/Umbraco.Core/IO/UmbracoMediaFile.cs diff --git a/src/Umbraco.Core/IO/ResizedImage.cs b/src/Umbraco.Core/IO/ResizedImage.cs new file mode 100644 index 0000000000..bd96dcfb26 --- /dev/null +++ b/src/Umbraco.Core/IO/ResizedImage.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Core.IO +{ + internal class ResizedImage + { + public ResizedImage() + { + } + + public ResizedImage(int width, int height, string fileName) + { + Width = width; + Height = height; + FileName = fileName; + } + + public int Width { get; set; } + public int Height { get; set; } + public string FileName { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/IO/UmbracoMediaFile.cs b/src/Umbraco.Core/IO/UmbracoMediaFile.cs new file mode 100644 index 0000000000..e657d9507a --- /dev/null +++ b/src/Umbraco.Core/IO/UmbracoMediaFile.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Web; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.IO +{ + public class UmbracoMediaFile + { + private readonly MediaFileSystem _fs; + + #region Constructors + + public UmbracoMediaFile() + { + _fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + } + + public UmbracoMediaFile(string path) + { + _fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + + Path = path; + + Initialize(); + } + + #endregion + + #region Static Methods + + //MB: Do we really need all these overloads? looking through the code, only one of them is actually used + + public static UmbracoMediaFile Save(HttpPostedFile file, string path) + { + return Save(file.InputStream, path); + } + + public static UmbracoMediaFile Save(HttpPostedFileBase file, string path) + { + return Save(file.InputStream, path); + } + + public static UmbracoMediaFile Save(Stream inputStream, string path) + { + var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + fs.AddFile(path, inputStream); + + return new UmbracoMediaFile(path); + } + + public static UmbracoMediaFile Save(byte[] file, string relativePath) + { + return Save(new MemoryStream(file), relativePath); + } + + public static UmbracoMediaFile Save(HttpPostedFile file) + { + var tempDir = System.IO.Path.Combine("uploads", Guid.NewGuid().ToString()); + return Save(file, tempDir); + } + + //filebase overload... + public static UmbracoMediaFile Save(HttpPostedFileBase file) + { + var tempDir = System.IO.Path.Combine("uploads", Guid.NewGuid().ToString()); + return Save(file, tempDir); + } + + #endregion + + private long? _length; + private Size? _size; + + /// + /// Initialized values that don't require opening the file. + /// + private void Initialize() + { + Filename = _fs.GetFileName(Path); + Extension = _fs.GetExtension(Path) != null + ? _fs.GetExtension(Path).Substring(1).ToLowerInvariant() + : ""; + Url = _fs.GetUrl(Path); + } + + public string Filename { get; private set; } + + public string Extension { get; private set; } + + public string Path { get; private set; } + + public string Url { get; private set; } + + /// + /// Get the length of the file in bytes + /// + /// + /// We are lazy loading this, don't go opening the file on ctor like we were doing. + /// + public long Length + { + get + { + if (_length == null) + { + _length = _fs.GetSize(Path); + } + return _length.Value; + } + } + + public bool SupportsResizing + { + get + { + return ("," + UmbracoSettings.ImageFileTypes + ",").Contains(string.Format(",{0},", Extension)); + } + } + + public string GetFriendlyName() + { + return Filename.SplitPascalCasing().ToFirstUpperInvariant(); + } + + public Size GetDimensions() + { + if (_size == null) + { + EnsureFileSupportsResizing(); + + var fs = _fs.OpenFile(Path); + var image = Image.FromStream(fs); + var fileWidth = image.Width; + var fileHeight = image.Height; + fs.Close(); + image.Dispose(); + + _size = new Size(fileWidth, fileHeight); + } + return _size.Value; + } + + public string Resize(int width, int height) + { + EnsureFileSupportsResizing(); + + var fileNameThumb = DoResize(width, height, 0, string.Empty); + + return _fs.GetUrl(fileNameThumb); + } + + public string Resize(int maxWidthHeight, string fileNameAddition) + { + EnsureFileSupportsResizing(); + + var fileNameThumb = DoResize(GetDimensions().Width, GetDimensions().Height, maxWidthHeight, fileNameAddition); + + return _fs.GetUrl(fileNameThumb); + } + + private string DoResize(int width, int height, int maxWidthHeight, string fileNameAddition) + { + using (var fs = _fs.OpenFile(Path)) + { + using (var image = Image.FromStream(fs)) + { + var fileNameThumb = string.IsNullOrWhiteSpace(fileNameAddition) + ? string.Format("{0}_UMBRACOSYSTHUMBNAIL.jpg", Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal))) + : string.Format("{0}_{1}.jpg", Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal)), fileNameAddition); + + var thumbnail = GenerateThumbnail(image, maxWidthHeight, width, height, fileNameThumb, maxWidthHeight == 0); + + return thumbnail.FileName; + } + } + } + + private void EnsureFileSupportsResizing() + { + if (SupportsResizing == false) + throw new InvalidOperationException(string.Format("The file {0} is not an image, so can't get dimensions", Filename)); + } + + private ResizedImage GenerateThumbnail(Image image, int maxWidthHeight, int fileWidth, int fileHeight, string thumbnailFileName, bool useFixedDimensions) + { + // Generate thumbnail + float f = 1; + if (useFixedDimensions == false) + { + var fx = (float)image.Size.Width / (float)maxWidthHeight; + var fy = (float)image.Size.Height / (float)maxWidthHeight; + + // must fit in thumbnail size + f = Math.Max(fx, fy); + } + + var widthTh = (int)Math.Round((float)fileWidth / f); + var heightTh = (int)Math.Round((float)fileHeight / f); + + // fixes for empty width or height + if (widthTh == 0) + widthTh = 1; + if (heightTh == 0) + heightTh = 1; + + // Create new image with best quality settings + using (var bp = new Bitmap(widthTh, heightTh)) + { + using (var g = Graphics.FromImage(bp)) + { + g.SmoothingMode = SmoothingMode.HighQuality; + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.CompositingQuality = CompositingQuality.HighQuality; + + // Copy the old image to the new and resized + var rect = new Rectangle(0, 0, widthTh, heightTh); + g.DrawImage(image, rect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel); + + // Copy metadata + var imageEncoders = ImageCodecInfo.GetImageEncoders(); + + var codec = Extension.ToLower() == "png" || Extension.ToLower() == "gif" + ? imageEncoders.Single(t => t.MimeType.Equals("image/png")) + : imageEncoders.Single(t => t.MimeType.Equals("image/jpeg")); + + // Set compresion ratio to 90% + var ep = new EncoderParameters(); + ep.Param[0] = new EncoderParameter(Encoder.Quality, 90L); + + // Save the new image using the dimensions of the image + var newFileName = thumbnailFileName.Replace("UMBRACOSYSTHUMBNAIL", string.Format("{0}x{1}", widthTh, heightTh)); + using (var ms = new MemoryStream()) + { + bp.Save(ms, codec, ep); + ms.Seek(0, 0); + + _fs.AddFile(newFileName, ms); + } + + return new ResizedImage(widthTh, heightTh, newFileName); + } + } + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5600a8da26..137a2bfbe5 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -173,6 +173,8 @@ + + diff --git a/src/umbraco.cms/businesslogic/Files/IFile.cs b/src/umbraco.cms/businesslogic/Files/IFile.cs index ccf858fb02..5f018b688f 100644 --- a/src/umbraco.cms/businesslogic/Files/IFile.cs +++ b/src/umbraco.cms/businesslogic/Files/IFile.cs @@ -5,6 +5,7 @@ using System.Text; namespace umbraco.cms.businesslogic.Files { + [Obsolete("This is no longer used ane will be removed from the codebase in the future")] public interface IFile { string Filename { get; } diff --git a/src/umbraco.cms/businesslogic/Files/NotAnImageException.cs b/src/umbraco.cms/businesslogic/Files/NotAnImageException.cs index 2d0ff67635..7ea71e2426 100644 --- a/src/umbraco.cms/businesslogic/Files/NotAnImageException.cs +++ b/src/umbraco.cms/businesslogic/Files/NotAnImageException.cs @@ -5,6 +5,7 @@ using System.Text; namespace umbraco.cms.businesslogic.Files { + [Obsolete("This is no longer used ane will be removed from the codebase in the future")] public class NotAnImageException : Exception { public NotAnImageException() diff --git a/src/umbraco.cms/businesslogic/Files/UmbracoFile.cs b/src/umbraco.cms/businesslogic/Files/UmbracoFile.cs index a37adad5b7..5822e8c72c 100644 --- a/src/umbraco.cms/businesslogic/Files/UmbracoFile.cs +++ b/src/umbraco.cms/businesslogic/Files/UmbracoFile.cs @@ -10,24 +10,26 @@ using Umbraco.Core.IO; namespace umbraco.cms.businesslogic.Files { + [Obsolete("Use Umbraco.Core.IO.UmbracoMediaFile instead")] public class UmbracoFile : IFile { - private readonly MediaFileSystem _fs; - + private readonly UmbracoMediaFile _mediaFile; + #region Constructors public UmbracoFile() { - _fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + _mediaFile = new UmbracoMediaFile(); } public UmbracoFile(string path) { - _fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + _mediaFile = new UmbracoMediaFile(path); + } - Path = path; - - Initialize(); + internal UmbracoFile(UmbracoMediaFile mediaFile) + { + _mediaFile = mediaFile; } #endregion @@ -38,57 +40,46 @@ namespace umbraco.cms.businesslogic.Files public static UmbracoFile Save(HttpPostedFile file, string path) { - return Save(file.InputStream, path); + return new UmbracoFile(UmbracoMediaFile.Save(file.InputStream, path)); } public static UmbracoFile Save(HttpPostedFileBase file, string path) { - return Save(file.InputStream, path); + return new UmbracoFile(UmbracoMediaFile.Save(file.InputStream, path)); } public static UmbracoFile Save(Stream inputStream, string path) { - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - fs.AddFile(path, inputStream); - - return new UmbracoFile(path); + return new UmbracoFile(UmbracoMediaFile.Save(inputStream, path)); } public static UmbracoFile Save(byte[] file, string relativePath) { - return Save(new MemoryStream(file), relativePath); + return new UmbracoFile(UmbracoMediaFile.Save(new MemoryStream(file), relativePath)); } public static UmbracoFile Save(HttpPostedFile file) { - var tempDir = System.IO.Path.Combine("uploads", Guid.NewGuid().ToString()); - return Save(file, tempDir); + return new UmbracoFile(UmbracoMediaFile.Save(file)); } //filebase overload... public static UmbracoFile Save(HttpPostedFileBase file) { - var tempDir = System.IO.Path.Combine("uploads", Guid.NewGuid().ToString()); - return Save(file, tempDir); + return new UmbracoFile(UmbracoMediaFile.Save(file)); } #endregion - - private void Initialize() + + public string Filename { - Filename = _fs.GetFileName(Path); - Length = _fs.GetSize(Path); - Extension = _fs.GetExtension(Path) != null - ? _fs.GetExtension(Path).Substring(1).ToLowerInvariant() - : ""; - Url = _fs.GetUrl(Path); + get { return _mediaFile.Filename; } } - #region IFile Members - - public string Filename { get; private set; } - - public string Extension { get; private set; } + public string Extension + { + get { return _mediaFile.Extension; } + } [Obsolete("LocalName is obsolete, please use Url instead", false)] public string LocalName @@ -96,160 +87,47 @@ namespace umbraco.cms.businesslogic.Files get { return Url; } } - public string Path { get; private set; } + public string Path + { + get { return _mediaFile.Path; } + } - public string Url { get; private set; } + public string Url + { + get { return _mediaFile.Url; } + } - public long Length { get; private set; } + public long Length + { + get { return _mediaFile.Length; } + } public bool SupportsResizing { - get - { - return ("," + UmbracoSettings.ImageFileTypes + ",").Contains(string.Format(",{0},", Extension)); - } + get { return _mediaFile.SupportsResizing; } } public string GetFriendlyName() { - return Filename.SplitPascalCasing().ToFirstUpperInvariant(); + return _mediaFile.GetFriendlyName(); } public System.Tuple GetDimensions() { - EnsureFileSupportsResizing(); - - var fs = _fs.OpenFile(Path); - var image = Image.FromStream(fs); - var fileWidth = image.Width; - var fileHeight = image.Height; - fs.Close(); - image.Dispose(); - - return new System.Tuple(fileWidth, fileHeight); + var size = _mediaFile.GetDimensions(); + return new System.Tuple(size.Width, size.Height); } public string Resize(int width, int height) { - EnsureFileSupportsResizing(); - - var fileNameThumb = DoResize(width, height, 0, string.Empty); - - return _fs.GetUrl(fileNameThumb); + return _mediaFile.Resize(width, height); } public string Resize(int maxWidthHeight, string fileNameAddition) { - EnsureFileSupportsResizing(); - - var fileNameThumb = DoResize(GetDimensions().Item1, GetDimensions().Item2, maxWidthHeight, fileNameAddition); - - return _fs.GetUrl(fileNameThumb); + return _mediaFile.Resize(maxWidthHeight, fileNameAddition); } - private string DoResize(int width, int height, int maxWidthHeight, string fileNameAddition) - { - using (var fs = _fs.OpenFile(Path)) - { - using (var image = Image.FromStream(fs)) - { - var fileNameThumb = string.IsNullOrWhiteSpace(fileNameAddition) - ? string.Format("{0}_UMBRACOSYSTHUMBNAIL.jpg", Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal))) - : string.Format("{0}_{1}.jpg", Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal)), fileNameAddition); - - var thumbnail = GenerateThumbnail(image, maxWidthHeight, width, height, fileNameThumb, maxWidthHeight == 0); - - return thumbnail.FileName; - } - } - } - - #endregion - - private void EnsureFileSupportsResizing() - { - if (SupportsResizing == false) - throw new NotAnImageException(string.Format("The file {0} is not an image, so can't get dimensions", Filename)); - } - - private ResizedImage GenerateThumbnail(Image image, int maxWidthHeight, int fileWidth, int fileHeight, string thumbnailFileName, bool useFixedDimensions) - { - // Generate thumbnail - float f = 1; - if (useFixedDimensions == false) - { - var fx = (float)image.Size.Width / (float)maxWidthHeight; - var fy = (float)image.Size.Height / (float)maxWidthHeight; - - // must fit in thumbnail size - f = Math.Max(fx, fy); - } - - var widthTh = (int)Math.Round((float)fileWidth / f); - var heightTh = (int)Math.Round((float)fileHeight / f); - - // fixes for empty width or height - if (widthTh == 0) - widthTh = 1; - if (heightTh == 0) - heightTh = 1; - - // Create new image with best quality settings - using (var bp = new Bitmap(widthTh, heightTh)) - { - using (var g = Graphics.FromImage(bp)) - { - g.SmoothingMode = SmoothingMode.HighQuality; - g.InterpolationMode = InterpolationMode.HighQualityBicubic; - g.PixelOffsetMode = PixelOffsetMode.HighQuality; - g.CompositingQuality = CompositingQuality.HighQuality; - - // Copy the old image to the new and resized - var rect = new Rectangle(0, 0, widthTh, heightTh); - g.DrawImage(image, rect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel); - - // Copy metadata - var imageEncoders = ImageCodecInfo.GetImageEncoders(); - - var codec = Extension.ToLower() == "png" || Extension.ToLower() == "gif" - ? imageEncoders.Single(t => t.MimeType.Equals("image/png")) - : imageEncoders.Single(t => t.MimeType.Equals("image/jpeg")); - - // Set compresion ratio to 90% - var ep = new EncoderParameters(); - ep.Param[0] = new EncoderParameter(Encoder.Quality, 90L); - - // Save the new image using the dimensions of the image - var newFileName = thumbnailFileName.Replace("UMBRACOSYSTHUMBNAIL", string.Format("{0}x{1}", widthTh, heightTh)); - using (var ms = new MemoryStream()) - { - bp.Save(ms, codec, ep); - ms.Seek(0, 0); - - _fs.AddFile(newFileName, ms); - } - - return new ResizedImage(widthTh, heightTh, newFileName); - } - } - } } - internal class ResizedImage - { - public ResizedImage() - { - } - - public ResizedImage(int width, int height, string fileName) - { - Width = width; - Height = height; - FileName = fileName; - } - - public int Width { get; set; } - public int Height { get; set; } - public string FileName { get; set; } - } } diff --git a/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs b/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs index 4a8786a8fd..53a7d14f24 100644 --- a/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs +++ b/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs @@ -169,12 +169,12 @@ namespace umbraco.cms.businesslogic.datatype object propertyValue) { XmlNode propertyNode = uploadFieldConfigNode.SelectSingleNode(propertyAlias); - if (propertyNode != null && !String.IsNullOrEmpty(propertyNode.FirstChild.Value)) + if (propertyNode != null && !string.IsNullOrEmpty(propertyNode.FirstChild.Value)) { - if (content.getProperty(propertyNode.FirstChild.Value) != null) + var prop = content.getProperty(propertyNode.FirstChild.Value); + if (prop != null) { - content.getProperty(propertyNode.FirstChild.Value) - .Value = propertyValue; + prop.Value = propertyValue; } } }