mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
#19581: Extracting media profiles logic from shapes
Work Item: 19581 This will make Zoltan happier too --HG-- branch : 1.x
This commit is contained in:
@@ -114,8 +114,10 @@
|
||||
<Compile Include="Models\ImageProfilePartRecord.cs" />
|
||||
<Compile Include="Providers\Filters\FormatFilter.cs" />
|
||||
<Compile Include="Providers\Filters\ResizeFilter.cs" />
|
||||
<Compile Include="Services\ImageProfileManager.cs" />
|
||||
<Compile Include="Services\IImageFilterProvider.cs" />
|
||||
<Compile Include="Services\IImageProcessingFileNameProvider.cs" />
|
||||
<Compile Include="Services\IImageProfileManager.cs" />
|
||||
<Compile Include="Services\IImageProfileService.cs" />
|
||||
<Compile Include="Services\IImageProcessingManager.cs" />
|
||||
<Compile Include="Services\ImageProcessingFileNameProvider.cs" />
|
||||
|
@@ -0,0 +1,11 @@
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.MediaProcessing.Models;
|
||||
|
||||
namespace Orchard.MediaProcessing.Services {
|
||||
public interface IImageProfileManager : IDependency {
|
||||
string GetImageProfileUrl(string path, string profileName);
|
||||
string GetImageProfileUrl(string path, string profileName, ContentItem contentItem);
|
||||
string GetImageProfileUrl(string path, string profileName, FilterRecord customFilter);
|
||||
string GetImageProfileUrl(string path, string profileName, FilterRecord customFilter, ContentItem contentItem);
|
||||
}
|
||||
}
|
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Web;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.FileSystems.Media;
|
||||
using Orchard.Forms.Services;
|
||||
using Orchard.Logging;
|
||||
using Orchard.MediaProcessing.Descriptors.Filter;
|
||||
using Orchard.MediaProcessing.Media;
|
||||
using Orchard.MediaProcessing.Models;
|
||||
using Orchard.Tokens;
|
||||
using Orchard.Utility.Extensions;
|
||||
|
||||
namespace Orchard.MediaProcessing.Services {
|
||||
public class ImageProfileManager : IImageProfileManager {
|
||||
private readonly IStorageProvider _storageProvider;
|
||||
private readonly IImageProcessingFileNameProvider _fileNameProvider;
|
||||
private readonly IImageProfileService _profileService;
|
||||
private readonly IImageProcessingManager _processingManager;
|
||||
private readonly IOrchardServices _services;
|
||||
private readonly ITokenizer _tokenizer;
|
||||
|
||||
public ImageProfileManager(
|
||||
IStorageProvider storageProvider,
|
||||
IImageProcessingFileNameProvider fileNameProvider,
|
||||
IImageProfileService profileService,
|
||||
IImageProcessingManager processingManager,
|
||||
IOrchardServices services,
|
||||
ITokenizer tokenizer) {
|
||||
_storageProvider = storageProvider;
|
||||
_fileNameProvider = fileNameProvider;
|
||||
_profileService = profileService;
|
||||
_processingManager = processingManager;
|
||||
_services = services;
|
||||
_tokenizer = tokenizer;
|
||||
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public string GetImageProfileUrl(string path, string profileName) {
|
||||
return GetImageProfileUrl(path, profileName, null, null);
|
||||
}
|
||||
|
||||
public string GetImageProfileUrl(string path, string profileName, ContentItem contentItem) {
|
||||
return GetImageProfileUrl(path, profileName, null, contentItem);
|
||||
}
|
||||
|
||||
public string GetImageProfileUrl(string path, string profileName, FilterRecord customFilter) {
|
||||
return GetImageProfileUrl(path, profileName, customFilter, null);
|
||||
}
|
||||
|
||||
public string GetImageProfileUrl(string path, string profileName, FilterRecord customFilter, ContentItem contentItem) {
|
||||
|
||||
// try to load the processed filename from cache
|
||||
var filePath = _fileNameProvider.GetFileName(profileName, path);
|
||||
bool process = false;
|
||||
|
||||
// if the filename is not cached, process it
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
Logger.Debug("FilePath is null, processing required, profile {0} for image {1}", profileName, path);
|
||||
|
||||
process = true;
|
||||
}
|
||||
|
||||
// the processd file doesn't exist anymore, process it
|
||||
else if (!_storageProvider.FileExists(filePath)) {
|
||||
Logger.Debug("Processed file no longer exists, processing required, profile {0} for image {1}", profileName, path);
|
||||
|
||||
process = true;
|
||||
}
|
||||
|
||||
// if the original file is more recent, process it
|
||||
else {
|
||||
DateTime pathLastUpdated;
|
||||
if (TryGetImageLastUpdated(path, out pathLastUpdated)) {
|
||||
var filePathLastUpdated = _storageProvider.GetFile(filePath).GetLastUpdated();
|
||||
|
||||
if (pathLastUpdated > filePathLastUpdated) {
|
||||
Logger.Debug("Original file more recent, processing required, profile {0} for image {1}", profileName, path);
|
||||
|
||||
process = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo: regenerate the file if the profile is newer, by deleting the associated filename cache entries.
|
||||
if (process) {
|
||||
Logger.Debug("Processing profile {0} for image {1}", profileName, path);
|
||||
|
||||
ImageProfilePart profilePart;
|
||||
|
||||
if (customFilter == null) {
|
||||
profilePart = _profileService.GetImageProfileByName(profileName);
|
||||
if (profilePart == null)
|
||||
return String.Empty;
|
||||
}
|
||||
else {
|
||||
profilePart = _services.ContentManager.New<ImageProfilePart>("ImageProfile");
|
||||
profilePart.Name = profileName;
|
||||
profilePart.Filters.Add(customFilter);
|
||||
}
|
||||
|
||||
using (var image = GetImage(path)) {
|
||||
|
||||
var filterContext = new FilterContext { Media = image, FilePath = _storageProvider.Combine("_Profiles", _storageProvider.Combine(profileName, CreateDefaultFileName(path))) };
|
||||
|
||||
var tokens = new Dictionary<string, object>();
|
||||
// if a content item is provided, use it while tokenizing
|
||||
if (contentItem != null) {
|
||||
tokens.Add("Content", contentItem);
|
||||
}
|
||||
|
||||
foreach (var filter in profilePart.Filters.OrderBy(f => f.Position)) {
|
||||
var descriptor = _processingManager.DescribeFilters().SelectMany(x => x.Descriptors).FirstOrDefault(x => x.Category == filter.Category && x.Type == filter.Type);
|
||||
if (descriptor == null)
|
||||
continue;
|
||||
|
||||
var tokenized = _tokenizer.Replace(filter.State, tokens);
|
||||
filterContext.State = FormParametersHelper.ToDynamic(tokenized);
|
||||
descriptor.Filter(filterContext);
|
||||
}
|
||||
|
||||
_fileNameProvider.UpdateFileName(profileName, path, filterContext.FilePath);
|
||||
|
||||
if (!filterContext.Saved) {
|
||||
_storageProvider.TryCreateFolder(_storageProvider.Combine("_Profiles", profilePart.Name));
|
||||
var newFile = _storageProvider.OpenOrCreate(filterContext.FilePath);
|
||||
using (var imageStream = newFile.OpenWrite()) {
|
||||
using (var sw = new BinaryWriter(imageStream)) {
|
||||
if (filterContext.Media.CanSeek) {
|
||||
filterContext.Media.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
using (var sr = new BinaryReader(filterContext.Media)) {
|
||||
int count;
|
||||
var buffer = new byte[8192];
|
||||
while ((count = sr.Read(buffer, 0, buffer.Length)) != 0) {
|
||||
sw.Write(buffer, 0, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filterContext.Media.Dispose();
|
||||
filePath = filterContext.FilePath;
|
||||
}
|
||||
}
|
||||
|
||||
// generate a timestamped url to force client caches to update if the file has changed
|
||||
var publicUrl = _storageProvider.GetPublicUrl(filePath);
|
||||
var timestamp = _storageProvider.GetFile(filePath).GetLastUpdated().Ticks;
|
||||
return publicUrl + "?v=" + timestamp.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
// TODO: Update this method once the storage provider has been updated
|
||||
private Stream GetImage(string path) {
|
||||
var request = _services.WorkContext.HttpContext.Request;
|
||||
|
||||
var storagePath = _storageProvider.GetStoragePath(path);
|
||||
if (storagePath != null) {
|
||||
var file = _storageProvider.GetFile(storagePath);
|
||||
return file.OpenRead();
|
||||
}
|
||||
|
||||
// http://blob.storage-provider.net/my-image.jpg
|
||||
if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) {
|
||||
return new WebClient().OpenRead(new Uri(path));
|
||||
}
|
||||
|
||||
// ~/Media/Default/images/my-image.jpg
|
||||
if (VirtualPathUtility.IsAppRelative(path)) {
|
||||
return new WebClient().OpenRead(new Uri(request.Url, VirtualPathUtility.ToAbsolute(path)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool TryGetImageLastUpdated(string path, out DateTime lastUpdated) {
|
||||
var storagePath = _storageProvider.GetStoragePath(path);
|
||||
if (storagePath != null) {
|
||||
var file = _storageProvider.GetFile(storagePath);
|
||||
lastUpdated = file.GetLastUpdated();
|
||||
return true;
|
||||
}
|
||||
|
||||
lastUpdated = DateTime.MinValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string CreateDefaultFileName(string path) {
|
||||
var extention = Path.GetExtension(path);
|
||||
var newPath = Path.ChangeExtension(path, "");
|
||||
newPath = newPath.Replace(@"/", "_");
|
||||
return newPath.ToSafeName() + extention;
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,46 +2,21 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Web;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.DisplayManagement;
|
||||
using Orchard.Environment;
|
||||
using Orchard.FileSystems.Media;
|
||||
using Orchard.Forms.Services;
|
||||
using Orchard.Logging;
|
||||
using Orchard.MediaProcessing.Descriptors.Filter;
|
||||
using Orchard.MediaProcessing.Media;
|
||||
using Orchard.MediaProcessing.Models;
|
||||
using Orchard.MediaProcessing.Services;
|
||||
using Orchard.Tokens;
|
||||
using Orchard.Utility.Extensions;
|
||||
|
||||
namespace Orchard.MediaProcessing.Shapes {
|
||||
|
||||
public class MediaShapes : IDependency {
|
||||
private readonly Work<IStorageProvider> _storageProvider;
|
||||
private readonly Work<IImageProcessingFileNameProvider> _fileNameProvider;
|
||||
private readonly Work<IImageProfileService> _profileService;
|
||||
private readonly Work<IImageProcessingManager> _processingManager;
|
||||
private readonly Work<IOrchardServices> _services;
|
||||
private readonly Work<ITokenizer> _tokenizer;
|
||||
private readonly Work<IImageProfileManager> _imageProfileManager;
|
||||
|
||||
public MediaShapes(
|
||||
Work<IStorageProvider> storageProvider,
|
||||
Work<IImageProcessingFileNameProvider> fileNameProvider,
|
||||
Work<IImageProfileService> profileService,
|
||||
Work<IImageProcessingManager> processingManager,
|
||||
Work<IOrchardServices> services,
|
||||
Work<ITokenizer> tokenizer) {
|
||||
_storageProvider = storageProvider;
|
||||
_fileNameProvider = fileNameProvider;
|
||||
_profileService = profileService;
|
||||
_processingManager = processingManager;
|
||||
_services = services;
|
||||
_tokenizer = tokenizer;
|
||||
Logger = NullLogger.Instance;
|
||||
public MediaShapes(Work<IImageProfileManager> imageProfileManager) {
|
||||
_imageProfileManager = imageProfileManager;
|
||||
}
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
@@ -76,155 +51,12 @@ namespace Orchard.MediaProcessing.Shapes {
|
||||
public void MediaUrl(dynamic Shape, dynamic Display, TextWriter Output, string Profile, string Path, ContentItem ContentItem, FilterRecord CustomFilter) {
|
||||
try {
|
||||
Shape.IgnoreShapeTracer = true;
|
||||
var services = new Lazy<IOrchardServices>(() => _services.Value);
|
||||
var storageProvider = new Lazy<IStorageProvider>(() => _storageProvider.Value);
|
||||
|
||||
// try to load the processed filename from cache
|
||||
var filePath = _fileNameProvider.Value.GetFileName(Profile, Path);
|
||||
bool process = false;
|
||||
|
||||
// if the filename is not cached, process it
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
Logger.Debug("FilePath is null, processing required, profile {0} for image {1}", Profile, Path);
|
||||
|
||||
process = true;
|
||||
}
|
||||
|
||||
// the processd file doesn't exist anymore, process it
|
||||
else if (!storageProvider.Value.FileExists(filePath)) {
|
||||
Logger.Debug("Processed file no longer exists, processing required, profile {0} for image {1}", Profile, Path);
|
||||
|
||||
process = true;
|
||||
}
|
||||
|
||||
// if the original file is more recent, process it
|
||||
else {
|
||||
DateTime pathLastUpdated;
|
||||
if (TryGetImageLastUpdated(Path, out pathLastUpdated)) {
|
||||
var filePathLastUpdated = storageProvider.Value.GetFile(filePath).GetLastUpdated();
|
||||
|
||||
if (pathLastUpdated > filePathLastUpdated)
|
||||
{
|
||||
Logger.Debug("Original file more recent, processing required, profile {0} for image {1}", Profile, Path);
|
||||
|
||||
process = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo: regenerate the file if the profile is newer, by deleting the associated filename cache entries.
|
||||
if (process) {
|
||||
Logger.Debug("Processing profile {0} for image {1}", Profile, Path);
|
||||
|
||||
ImageProfilePart profilePart;
|
||||
|
||||
if (CustomFilter == null) {
|
||||
profilePart = _profileService.Value.GetImageProfileByName(Profile);
|
||||
if (profilePart == null)
|
||||
return;
|
||||
}
|
||||
else {
|
||||
profilePart = services.Value.ContentManager.New<ImageProfilePart>("ImageProfile");
|
||||
profilePart.Name = Profile;
|
||||
profilePart.Filters.Add(CustomFilter);
|
||||
}
|
||||
|
||||
using (var image = GetImage(Path)) {
|
||||
|
||||
var filterContext = new FilterContext { Media = image, FilePath = storageProvider.Value.Combine("_Profiles", storageProvider.Value.Combine(Profile, CreateDefaultFileName(Path))) };
|
||||
|
||||
var tokens = new Dictionary<string, object>();
|
||||
// if a content item is provided, use it while tokenizing
|
||||
if (ContentItem != null) {
|
||||
tokens.Add("Content", ContentItem);
|
||||
}
|
||||
|
||||
foreach (var filter in profilePart.Filters.OrderBy(f => f.Position)) {
|
||||
var descriptor = _processingManager.Value.DescribeFilters().SelectMany(x => x.Descriptors).FirstOrDefault(x => x.Category == filter.Category && x.Type == filter.Type);
|
||||
if (descriptor == null)
|
||||
continue;
|
||||
|
||||
var tokenized = _tokenizer.Value.Replace(filter.State, tokens);
|
||||
filterContext.State = FormParametersHelper.ToDynamic(tokenized);
|
||||
descriptor.Filter(filterContext);
|
||||
}
|
||||
|
||||
_fileNameProvider.Value.UpdateFileName(Profile, Path, filterContext.FilePath);
|
||||
|
||||
if (!filterContext.Saved) {
|
||||
storageProvider.Value.TryCreateFolder(storageProvider.Value.Combine("_Profiles", profilePart.Name));
|
||||
var newFile = storageProvider.Value.OpenOrCreate(filterContext.FilePath);
|
||||
using (var imageStream = newFile.OpenWrite()) {
|
||||
using (var sw = new BinaryWriter(imageStream)) {
|
||||
if (filterContext.Media.CanSeek) {
|
||||
filterContext.Media.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
using (var sr = new BinaryReader(filterContext.Media)) {
|
||||
int count;
|
||||
var buffer = new byte[8192];
|
||||
while ((count = sr.Read(buffer, 0, buffer.Length)) != 0) {
|
||||
sw.Write(buffer, 0, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filterContext.Media.Dispose();
|
||||
filePath = filterContext.FilePath;
|
||||
}
|
||||
}
|
||||
|
||||
// generate a timestamped url to force client caches to update if the file has changed
|
||||
var publicUrl = storageProvider.Value.GetPublicUrl(filePath);
|
||||
var timestamp = storageProvider.Value.GetFile(filePath).GetLastUpdated().Ticks;
|
||||
Output.Write(publicUrl + "?v=" + timestamp.ToString(CultureInfo.InvariantCulture));
|
||||
Output.Write(_imageProfileManager.Value.GetImageProfileUrl(Path, Profile, CustomFilter, ContentItem));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Logger.Error(ex, "An error occured while rendering shape {0} for image {1}", Profile, Path);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Update this method once the storage provider has been updated
|
||||
private Stream GetImage(string path) {
|
||||
var request = _services.Value.WorkContext.HttpContext.Request;
|
||||
|
||||
var storagePath = _storageProvider.Value.GetStoragePath(path);
|
||||
if (storagePath != null) {
|
||||
var file = _storageProvider.Value.GetFile(storagePath);
|
||||
return file.OpenRead();
|
||||
}
|
||||
|
||||
// http://blob.storage-provider.net/my-image.jpg
|
||||
if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) {
|
||||
return new WebClient().OpenRead(new Uri(path));
|
||||
}
|
||||
|
||||
// ~/Media/Default/images/my-image.jpg
|
||||
if (VirtualPathUtility.IsAppRelative(path)) {
|
||||
return new WebClient().OpenRead(new Uri(request.Url, VirtualPathUtility.ToAbsolute(path)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool TryGetImageLastUpdated(string path, out DateTime lastUpdated) {
|
||||
var storagePath = _storageProvider.Value.GetStoragePath(path);
|
||||
if (storagePath != null) {
|
||||
var file = _storageProvider.Value.GetFile(storagePath);
|
||||
lastUpdated = file.GetLastUpdated();
|
||||
return true;
|
||||
}
|
||||
|
||||
lastUpdated = DateTime.MinValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string CreateDefaultFileName(string path) {
|
||||
var extention = Path.GetExtension(path);
|
||||
var newPath = Path.ChangeExtension(path, "");
|
||||
newPath = newPath.Replace(@"/", "_");
|
||||
return newPath.ToSafeName() + extention;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user