2025-10-31 21:06:10 +01:00
|
|
|
using System;
|
2015-12-13 22:21:02 +01:00
|
|
|
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;
|
2024-02-29 19:39:32 +01:00
|
|
|
using Orchard.MediaLibrary.Models;
|
2015-12-13 22:21:02 +01:00
|
|
|
using Orchard.MediaProcessing.Descriptors.Filter;
|
|
|
|
|
using Orchard.MediaProcessing.Media;
|
|
|
|
|
using Orchard.MediaProcessing.Models;
|
|
|
|
|
using Orchard.Tokens;
|
|
|
|
|
using Orchard.Utility.Extensions;
|
|
|
|
|
|
2025-10-31 21:06:10 +01:00
|
|
|
namespace Orchard.MediaProcessing.Services
|
|
|
|
|
{
|
|
|
|
|
public class ImageProfileManager : IImageProfileManager
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
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,
|
2025-10-31 21:06:10 +01:00
|
|
|
ITokenizer tokenizer)
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
_storageProvider = storageProvider;
|
|
|
|
|
_fileNameProvider = fileNameProvider;
|
|
|
|
|
_profileService = profileService;
|
|
|
|
|
_processingManager = processingManager;
|
|
|
|
|
_services = services;
|
|
|
|
|
_tokenizer = tokenizer;
|
|
|
|
|
|
|
|
|
|
Logger = NullLogger.Instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ILogger Logger { get; set; }
|
|
|
|
|
|
2025-10-31 21:06:10 +01:00
|
|
|
public string GetImageProfileUrl(string path, string profileName)
|
|
|
|
|
{
|
2015-12-13 23:46:19 +01:00
|
|
|
return GetImageProfileUrl(path, profileName, null, new FilterRecord[] { });
|
2015-12-13 22:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:06:10 +01:00
|
|
|
public string GetImageProfileUrl(string path, string profileName, ContentItem contentItem)
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
return GetImageProfileUrl(path, profileName, null, contentItem);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:06:10 +01:00
|
|
|
public string GetImageProfileUrl(string path, string profileName, FilterRecord customFilter)
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
return GetImageProfileUrl(path, profileName, customFilter, null);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:06:10 +01:00
|
|
|
public string GetImageProfileUrl(string path, string profileName, FilterRecord customFilter, ContentItem contentItem)
|
|
|
|
|
{
|
2016-02-09 13:50:09 +03:00
|
|
|
var customFilters = customFilter != null ? new FilterRecord[] { customFilter } : null;
|
|
|
|
|
return GetImageProfileUrl(path, profileName, contentItem, customFilters);
|
2015-12-13 23:46:19 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:06:10 +01:00
|
|
|
public string GetImageProfileUrl(string path, string profileName, ContentItem contentItem, params FilterRecord[] customFilters)
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
|
|
|
|
|
// path is the publicUrl of the media, so it might contain url-encoded chars
|
|
|
|
|
|
|
|
|
|
// try to load the processed filename from cache
|
2024-05-03 13:19:20 +00:00
|
|
|
var filePath = _fileNameProvider.GetFileName(profileName, HttpUtility.UrlDecode(path));
|
2015-12-13 22:21:02 +01:00
|
|
|
bool process = false;
|
|
|
|
|
|
2024-02-29 19:39:32 +01:00
|
|
|
// Before checking everything else, ensure that the content item that needs to be processed has a ImagePart.
|
|
|
|
|
// If it's not the case (e.g. if media is a svg file), processing would throw a exception.
|
|
|
|
|
// If content item is null (it means it's not passed as a parameter of the ResizeMediaUrl call),
|
|
|
|
|
// this function processes the file like it did before this patch;
|
|
|
|
|
// this means it could possibly throw and log exceptions for svg files.
|
2025-10-31 21:43:05 +01:00
|
|
|
bool checkForProfile = contentItem == null || contentItem.Has<ImagePart>();
|
2024-02-29 19:39:32 +01:00
|
|
|
|
2025-10-31 21:06:10 +01:00
|
|
|
if (checkForProfile)
|
|
|
|
|
{
|
2024-02-29 19:39:32 +01:00
|
|
|
//after reboot the app cache is empty so we reload the image in the cache if it exists in the _Profiles folder
|
2025-10-31 21:06:10 +01:00
|
|
|
if (string.IsNullOrEmpty(filePath))
|
|
|
|
|
{
|
2024-05-03 13:19:20 +00:00
|
|
|
var profileFilePath = _storageProvider.Combine("_Profiles", FormatProfilePath(profileName, HttpUtility.UrlDecode(path)));
|
2024-02-29 19:39:32 +01:00
|
|
|
|
2025-10-31 21:06:10 +01:00
|
|
|
if (_storageProvider.FileExists(profileFilePath))
|
|
|
|
|
{
|
2024-05-03 13:19:20 +00:00
|
|
|
_fileNameProvider.UpdateFileName(profileName, HttpUtility.UrlDecode(path), profileFilePath);
|
2024-02-29 19:39:32 +01:00
|
|
|
filePath = profileFilePath;
|
|
|
|
|
}
|
2015-12-13 22:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
2024-02-29 19:39:32 +01:00
|
|
|
// if the filename is not cached, process it
|
2025-10-31 21:06:10 +01:00
|
|
|
if (string.IsNullOrEmpty(filePath))
|
|
|
|
|
{
|
2024-02-29 19:39:32 +01:00
|
|
|
Logger.Debug("FilePath is null, processing required, profile {0} for image {1}", profileName, path);
|
|
|
|
|
|
|
|
|
|
process = true;
|
|
|
|
|
}
|
2015-12-13 22:21:02 +01:00
|
|
|
// the processd file doesn't exist anymore, process it
|
2025-10-31 21:06:10 +01:00
|
|
|
else if (!_storageProvider.FileExists(filePath))
|
|
|
|
|
{
|
2024-02-29 19:39:32 +01:00
|
|
|
Logger.Debug("Processed file no longer exists, processing required, profile {0} for image {1}", profileName, path);
|
2015-12-13 22:21:02 +01:00
|
|
|
|
2024-02-29 19:39:32 +01:00
|
|
|
process = true;
|
|
|
|
|
}
|
|
|
|
|
// if the original file is more recent, process it
|
2025-10-31 21:06:10 +01:00
|
|
|
else if (TryGetImageLastUpdated(path, out DateTime pathLastUpdated))
|
|
|
|
|
{
|
2024-05-03 13:19:20 +00:00
|
|
|
var filePathLastUpdated = _storageProvider.GetFile(filePath).GetLastUpdated();
|
2015-12-13 22:21:02 +01:00
|
|
|
|
2025-10-31 21:06:10 +01:00
|
|
|
if (pathLastUpdated > filePathLastUpdated)
|
|
|
|
|
{
|
2024-05-03 13:19:20 +00:00
|
|
|
Logger.Debug("Original file more recent, processing required, profile {0} for image {1}", profileName, path);
|
2015-12-13 22:21:02 +01:00
|
|
|
|
2024-05-03 13:19:20 +00:00
|
|
|
process = true;
|
2015-12-13 22:21:02 +01:00
|
|
|
}
|
|
|
|
|
}
|
2024-05-03 13:19:20 +00:00
|
|
|
}
|
2025-10-31 21:06:10 +01:00
|
|
|
else
|
|
|
|
|
{
|
2024-02-29 19:39:32 +01:00
|
|
|
// Since media with no ImagePart have no profile, filePath is null, so it's set again to its original path on the storage provider.
|
2025-10-31 21:06:10 +01:00
|
|
|
if (string.IsNullOrWhiteSpace(filePath))
|
|
|
|
|
{
|
2024-02-29 19:39:32 +01:00
|
|
|
filePath = _storageProvider.GetStoragePath(path);
|
|
|
|
|
}
|
2015-12-13 22:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// todo: regenerate the file if the profile is newer, by deleting the associated filename cache entries.
|
2025-10-31 21:06:10 +01:00
|
|
|
if (process)
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
Logger.Debug("Processing profile {0} for image {1}", profileName, path);
|
|
|
|
|
|
|
|
|
|
ImageProfilePart profilePart;
|
|
|
|
|
|
2025-10-31 21:06:10 +01:00
|
|
|
if (customFilters == null || !customFilters.Any(c => c != null))
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
profilePart = _profileService.GetImageProfileByName(profileName);
|
2025-10-31 21:06:10 +01:00
|
|
|
if (profilePart == null)
|
|
|
|
|
{
|
2024-05-03 13:19:20 +00:00
|
|
|
return string.Empty;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-31 21:06:10 +01:00
|
|
|
else
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
profilePart = _services.ContentManager.New<ImageProfilePart>("ImageProfile");
|
|
|
|
|
profilePart.Name = profileName;
|
2025-10-31 21:06:10 +01:00
|
|
|
foreach (var customFilter in customFilters)
|
|
|
|
|
{
|
2015-12-13 23:46:19 +01:00
|
|
|
profilePart.Filters.Add(customFilter);
|
|
|
|
|
}
|
2015-12-13 22:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// prevent two requests from processing the same file at the same time
|
|
|
|
|
// this is only thread safe at the machine level, so there is a try/catch later
|
|
|
|
|
// to handle cross machines concurrency
|
2025-10-31 21:06:10 +01:00
|
|
|
lock (string.Intern(path))
|
|
|
|
|
{
|
|
|
|
|
using (var image = GetImage(path))
|
|
|
|
|
{
|
|
|
|
|
if (image == null)
|
|
|
|
|
{
|
2017-12-07 20:07:49 +00:00
|
|
|
return null;
|
2015-12-13 22:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
2024-05-03 13:19:20 +00:00
|
|
|
var filterContext = new FilterContext { Media = image, FilePath = _storageProvider.Combine("_Profiles", FormatProfilePath(profileName, HttpUtility.UrlDecode(path))) };
|
2017-12-07 20:07:49 +00:00
|
|
|
|
2015-12-13 22:21:02 +01:00
|
|
|
var tokens = new Dictionary<string, object>();
|
|
|
|
|
// if a content item is provided, use it while tokenizing
|
2025-10-31 21:06:10 +01:00
|
|
|
if (contentItem != null)
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
tokens.Add("Content", contentItem);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:06:10 +01:00
|
|
|
foreach (var filter in profilePart.Filters.OrderBy(f => f.Position))
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-03 13:19:20 +00:00
|
|
|
_fileNameProvider.UpdateFileName(profileName, HttpUtility.UrlDecode(path), filterContext.FilePath);
|
2015-12-13 22:21:02 +01:00
|
|
|
|
2025-10-31 21:06:10 +01:00
|
|
|
if (!filterContext.Saved)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
var newFile = _storageProvider.OpenOrCreate(filterContext.FilePath);
|
2025-10-31 21:06:10 +01:00
|
|
|
using (var imageStream = newFile.OpenWrite())
|
|
|
|
|
{
|
|
|
|
|
using (var sw = new BinaryWriter(imageStream))
|
|
|
|
|
{
|
|
|
|
|
if (filterContext.Media.CanSeek)
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
filterContext.Media.Seek(0, SeekOrigin.Begin);
|
|
|
|
|
}
|
2025-10-31 21:06:10 +01:00
|
|
|
using (var sr = new BinaryReader(filterContext.Media))
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
int count;
|
|
|
|
|
var buffer = new byte[8192];
|
2025-10-31 21:06:10 +01:00
|
|
|
while ((count = sr.Read(buffer, 0, buffer.Length)) != 0)
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
sw.Write(buffer, 0, count);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-09-05 09:01:30 +02:00
|
|
|
// the storage provider may have altered the filepath
|
|
|
|
|
filterContext.FilePath = newFile.GetPath();
|
2015-12-13 22:21:02 +01:00
|
|
|
}
|
2024-05-03 13:19:20 +00:00
|
|
|
}
|
2025-10-31 21:06:10 +01:00
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
Logger.Error(e, "A profile could not be processed: " + path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2025-10-31 21:06:10 +01:00
|
|
|
private Stream GetImage(string path)
|
|
|
|
|
{
|
|
|
|
|
if (path == null)
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
throw new ArgumentNullException("path");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var storagePath = _storageProvider.GetStoragePath(path);
|
2025-10-31 21:06:10 +01:00
|
|
|
if (storagePath != null)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
var file = _storageProvider.GetFile(storagePath);
|
|
|
|
|
return file.OpenRead();
|
2024-05-03 13:19:20 +00:00
|
|
|
}
|
2025-10-31 21:06:10 +01:00
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
Logger.Error(e, "path:" + path + " storagePath:" + storagePath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// http://blob.storage-provider.net/my-image.jpg
|
2025-10-31 21:06:10 +01:00
|
|
|
if (Uri.TryCreate(path, UriKind.Absolute, out Uri absoluteUri))
|
|
|
|
|
{
|
2016-05-12 15:30:19 -04:00
|
|
|
return new WebClient().OpenRead(absoluteUri);
|
2015-12-13 22:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ~/Media/Default/images/my-image.jpg
|
2025-10-31 21:06:10 +01:00
|
|
|
if (VirtualPathUtility.IsAppRelative(path))
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
var request = _services.WorkContext.HttpContext.Request;
|
|
|
|
|
return new WebClient().OpenRead(new Uri(request.Url, VirtualPathUtility.ToAbsolute(path)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:06:10 +01:00
|
|
|
private bool TryGetImageLastUpdated(string path, out DateTime lastUpdated)
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
var storagePath = _storageProvider.GetStoragePath(path);
|
2025-10-31 21:06:10 +01:00
|
|
|
if (storagePath != null)
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
var file = _storageProvider.GetFile(storagePath);
|
|
|
|
|
lastUpdated = file.GetLastUpdated();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lastUpdated = DateTime.MinValue;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:06:10 +01:00
|
|
|
private string FormatProfilePath(string profileName, string path)
|
|
|
|
|
{
|
2015-12-13 22:21:02 +01:00
|
|
|
var filenameWithExtension = Path.GetFileName(path) ?? "";
|
|
|
|
|
var fileLocation = path.Substring(0, path.Length - filenameWithExtension.Length);
|
|
|
|
|
|
|
|
|
|
return _storageProvider.Combine(
|
2024-05-03 13:19:20 +00:00
|
|
|
_storageProvider.Combine(_profileService.GetNameHashCode(profileName), _profileService.GetNameHashCode(fileLocation)),
|
|
|
|
|
filenameWithExtension);
|
2015-12-13 22:21:02 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|