From 6561e297fe03ee9ecbb04fa90253e375eea78989 Mon Sep 17 00:00:00 2001 From: Arjan Noordende Date: Fri, 17 Jun 2016 14:41:49 +0100 Subject: [PATCH 1/7] #6981 Normalized image profile path hash and added profile purging --- .../Controllers/AdminController.cs | 33 +++++++++ .../Services/IImageProfileManager.cs | 3 + .../Services/ImageProfileManager.cs | 70 +++++++++++++++++-- .../Views/Admin/Index.cshtml | 6 +- 4 files changed, 106 insertions(+), 6 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Controllers/AdminController.cs index 9c92d78940e..39b25756e19 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Controllers/AdminController.cs @@ -22,6 +22,7 @@ namespace Orchard.MediaProcessing.Controllers { public class AdminController : Controller, IUpdateModel { private readonly ISiteService _siteService; private readonly IImageProfileService _profileService; + private readonly IImageProfileManager _imageProfileManager; private readonly IImageProcessingManager _imageProcessingManager; public AdminController( @@ -29,9 +30,11 @@ public AdminController( IShapeFactory shapeFactory, ISiteService siteService, IImageProfileService profileService, + IImageProfileManager imageProfileManager, IImageProcessingManager imageProcessingManager) { _siteService = siteService; _profileService = profileService; + _imageProfileManager = imageProfileManager; _imageProcessingManager = imageProcessingManager; Services = services; @@ -185,6 +188,36 @@ public ActionResult Preview(int id) { throw new NotImplementedException(); } + [HttpPost] + public ActionResult Purge(int id) { + if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to manage media profiles"))) + return new HttpUnauthorizedResult(); + + if (_imageProfileManager.PurgeImageProfile(id)) { + Services.Notifier.Information(T("The Image Profile has been purged")); + } + else { + Services.Notifier.Warning(T("Unable to purge the Image Profile, it may already have been purged")); + } + + return RedirectToAction("Index"); + } + + [HttpPost] + public ActionResult PurgeObsolete() { + if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to manage media profiles"))) + return new HttpUnauthorizedResult(); + + if (_imageProfileManager.PurgeObsoleteImageProfiles()) { + Services.Notifier.Information(T("The obsolete Image Profiles have been purged")); + } + else { + Services.Notifier.Warning(T("Unable to purge the obsolete Image Profiles")); + } + + return RedirectToAction("Index"); + } + bool IUpdateModel.TryUpdateModel(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) { return TryUpdateModel(model, prefix, includeProperties, excludeProperties); } diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/IImageProfileManager.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/IImageProfileManager.cs index 23de6ea267d..4bd8e9207d4 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/IImageProfileManager.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/IImageProfileManager.cs @@ -8,5 +8,8 @@ public interface IImageProfileManager : IDependency { string GetImageProfileUrl(string path, string profileName, FilterRecord customFilter); string GetImageProfileUrl(string path, string profileName, FilterRecord customFilter, ContentItem contentItem); string GetImageProfileUrl(string path, string profileName, ContentItem contentItem, params FilterRecord[] customFilters); + + bool PurgeImageProfile(int id); + bool PurgeObsoleteImageProfiles(); } } diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs index c09d7bbfd71..3c6b54995b4 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs @@ -15,6 +15,8 @@ using Orchard.MediaProcessing.Models; using Orchard.Tokens; using Orchard.Utility.Extensions; +using Orchard.Environment.Configuration; +using Orchard.Caching; namespace Orchard.MediaProcessing.Services { public class ImageProfileManager : IImageProfileManager { @@ -24,6 +26,8 @@ public class ImageProfileManager : IImageProfileManager { private readonly IImageProcessingManager _processingManager; private readonly IOrchardServices _services; private readonly ITokenizer _tokenizer; + private readonly ISignals _signals; + private readonly IAppConfigurationAccessor _appConfigurationAccessor; public ImageProfileManager( IStorageProvider storageProvider, @@ -31,13 +35,17 @@ public ImageProfileManager( IImageProfileService profileService, IImageProcessingManager processingManager, IOrchardServices services, - ITokenizer tokenizer) { + ITokenizer tokenizer, + ISignals signals, + IAppConfigurationAccessor appConfigurationAccessor) { _storageProvider = storageProvider; _fileNameProvider = fileNameProvider; _profileService = profileService; _processingManager = processingManager; _services = services; _tokenizer = tokenizer; + _signals = signals; + _appConfigurationAccessor = appConfigurationAccessor; Logger = NullLogger.Instance; } @@ -235,14 +243,66 @@ private bool TryGetImageLastUpdated(string path, out DateTime lastUpdated) { return false; } + public bool PurgeImageProfile(int id) { + var profile = _profileService.GetImageProfile(id); + try { + var folder = _storageProvider.Combine("_Profiles", GetHexHashCode(profile.Name)); + _storageProvider.DeleteFolder(folder); + profile.FileNames.Clear(); + _signals.Trigger("MediaProcessing_Saved_" + profile.Name); + return true; + } + catch (Exception ex) { + Logger.Warning(ex, "Unable to purge image profile '{0}'", profile.Name); + return false; + } + } + + public bool PurgeObsoleteImageProfiles() { + var profiles = _profileService.GetAllImageProfiles(); + try { + if (profiles != null) { + var validPaths = profiles.Select(profile => _storageProvider.Combine("_Profiles", GetHexHashCode(profile.Name))); + foreach (var folder in _storageProvider.ListFolders("_Profiles").Select(f => f.GetPath())) { + if (!validPaths.Any(v => folder.StartsWith(v))) { + _storageProvider.DeleteFolder(folder); + } + } + } + return true; + } + catch (Exception ex) { + Logger.Warning(ex, "Unable to purge obsolete image profiles"); + return false; + } + } + private string FormatProfilePath(string profileName, string path) { - - var filenameWithExtension = Path.GetFileName(path) ?? ""; - var fileLocation = path.Substring(0, path.Length - filenameWithExtension.Length); + var normalizedPath = ShouldNormalizePath() ? NormalizePath(path) : path; + var filenameWithExtension = Path.GetFileName(normalizedPath) ?? ""; + var fileLocation = normalizedPath.Substring(0, normalizedPath.Length - filenameWithExtension.Length); return _storageProvider.Combine( - _storageProvider.Combine(profileName.GetHashCode().ToString("x").ToLowerInvariant(), fileLocation.GetHashCode().ToString("x").ToLowerInvariant()), + _storageProvider.Combine(GetHexHashCode(profileName), GetHexHashCode(fileLocation)), filenameWithExtension); } + + private string GetHexHashCode(string value) { + return value.GetHashCode().ToString("x").ToLowerInvariant(); + } + + private string NormalizePath(string path) { + //Slice at the protocol, if any. E.g. http:// or https://. + var index = path.IndexOf("//", StringComparison.OrdinalIgnoreCase); + //Slice at the first directory after the protocol or at 0 if no protocol specified. + index = path.IndexOf("/", index < 0 ? 0 : index + 2, StringComparison.OrdinalIgnoreCase); + //Return path from the first directory, replacing lcase 'media' (Azure container) with ucase 'Media' (filestorage). + return path.Substring(index < 1 ? 0 : index).Replace("media", "Media"); + } + + private bool ShouldNormalizePath() { + var normalizePath = _appConfigurationAccessor.GetConfiguration("Orchard.MediaProcessing.NormalizePath"); + return string.IsNullOrEmpty(normalizePath) || normalizePath.Equals("true", StringComparison.OrdinalIgnoreCase); + } } } diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Views/Admin/Index.cshtml b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Views/Admin/Index.cshtml index bd6efd60a5d..c0b52fe1184 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Views/Admin/Index.cshtml +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Views/Admin/Index.cshtml @@ -14,7 +14,10 @@ @using (Html.BeginFormAntiForgeryPost()) { @Html.ValidationSummary() -
@Html.ActionLink(T("Add a new Media Profile").ToString(), "Create", new { Area = "Contents", id = "ImageProfile", returnurl = HttpContext.Current.Request.RawUrl }, new { @class = "button primaryAction" })
+
+ @Html.ActionLink(T("Purge Obsolete").ToString(), "PurgeObsolete", null, new { itemprop = "UnsafeUrl", @class = "button remove", data_unsafe_url = @T("Are you sure you wish to purge all obsolete profile images and force all dynamic profile images to be regenerated?").ToString() }) + @Html.ActionLink(T("Add a new Media Profile").ToString(), "Create", new { Area = "Contents", id = "ImageProfile", returnurl = HttpContext.Current.Request.RawUrl }, new { @class = "button primaryAction" }) +
@@ -56,6 +59,7 @@ @Html.ActionLink(T("Properties").ToString(), "Edit", new { Area = "Contents", id = entry.ImageProfileId, returnurl = HttpContext.Current.Request.RawUrl }) | @Html.ActionLink(T("Edit").ToString(), "Edit", new { id = entry.ImageProfileId }) | + @Html.ActionLink(T("Purge").ToString(), "Purge", new { id = entry.ImageProfileId }, new { itemprop = "UnsafeUrl", data_unsafe_url = @T("Are you sure you wish to purge all images for this profile?").ToString() }) | @Html.ActionLink(T("Delete").ToString(), "Delete", new { id = entry.ImageProfileId }, new { itemprop = "RemoveUrl UnsafeUrl" }) @*@Html.ActionLink(T("Preview").ToString(), "Preview", new { id = entry.ImageProfileId })*@ From 32b8905af985586e3a49ee87ea5af7e79402d1c1 Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Tue, 9 Apr 2024 18:13:32 +0200 Subject: [PATCH 2/7] Profile purge functions rather belong in IImageProfileService --- .../Controllers/AdminController.cs | 13 ++-- .../Models/ImageProfilePart.cs | 2 +- .../Services/IImageProfileManager.cs | 3 - .../Services/IImageProfileService.cs | 9 ++- .../Services/ImageProfileManager.cs | 69 ++++--------------- .../Services/ImageProfileService.cs | 53 +++++++++++--- 6 files changed, 72 insertions(+), 77 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Controllers/AdminController.cs index 39b25756e19..4d350f15d79 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Controllers/AdminController.cs @@ -22,7 +22,6 @@ namespace Orchard.MediaProcessing.Controllers { public class AdminController : Controller, IUpdateModel { private readonly ISiteService _siteService; private readonly IImageProfileService _profileService; - private readonly IImageProfileManager _imageProfileManager; private readonly IImageProcessingManager _imageProcessingManager; public AdminController( @@ -30,11 +29,9 @@ public AdminController( IShapeFactory shapeFactory, ISiteService siteService, IImageProfileService profileService, - IImageProfileManager imageProfileManager, IImageProcessingManager imageProcessingManager) { _siteService = siteService; _profileService = profileService; - _imageProfileManager = imageProfileManager; _imageProcessingManager = imageProcessingManager; Services = services; @@ -91,7 +88,7 @@ public ActionResult Index(FormCollection input) { if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to manage media profiles"))) return new HttpUnauthorizedResult(); - var viewModel = new AdminIndexViewModel {ImageProfiles = new List(), Options = new AdminIndexOptions()}; + var viewModel = new AdminIndexViewModel { ImageProfiles = new List(), Options = new AdminIndexOptions() }; UpdateModel(viewModel); var checkedItems = viewModel.ImageProfiles.Where(c => c.IsChecked); @@ -136,7 +133,7 @@ public ActionResult Edit(int id) { Category = f.Category, Type = f.Type, FilterRecordId = filter.Id, - DisplayText = String.IsNullOrWhiteSpace(filter.Description) ? f.Display(new FilterContext {State = FormParametersHelper.ToDynamic(filter.State)}).Text : filter.Description + DisplayText = String.IsNullOrWhiteSpace(filter.Description) ? f.Display(new FilterContext { State = FormParametersHelper.ToDynamic(filter.State) }).Text : filter.Description }); } } @@ -178,7 +175,7 @@ public ActionResult Move(string direction, int id, int filterId) { throw new ArgumentException("direction"); } - return RedirectToAction("Edit", new {id}); + return RedirectToAction("Edit", new { id }); } public ActionResult Preview(int id) { @@ -193,7 +190,7 @@ public ActionResult Purge(int id) { if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to manage media profiles"))) return new HttpUnauthorizedResult(); - if (_imageProfileManager.PurgeImageProfile(id)) { + if (_profileService.PurgeImageProfile(id)) { Services.Notifier.Information(T("The Image Profile has been purged")); } else { @@ -208,7 +205,7 @@ public ActionResult PurgeObsolete() { if (!Services.Authorizer.Authorize(StandardPermissions.SiteOwner, T("Not authorized to manage media profiles"))) return new HttpUnauthorizedResult(); - if (_imageProfileManager.PurgeObsoleteImageProfiles()) { + if (_profileService.PurgeObsoleteImageProfiles()) { Services.Notifier.Information(T("The obsolete Image Profiles have been purged")); } else { diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Models/ImageProfilePart.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Models/ImageProfilePart.cs index a8de5d8fe8a..1b432036b3c 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Models/ImageProfilePart.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Models/ImageProfilePart.cs @@ -24,4 +24,4 @@ public IList FileNames { get { return Record.FileNames; } } } -} \ No newline at end of file +} diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/IImageProfileManager.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/IImageProfileManager.cs index 4bd8e9207d4..23de6ea267d 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/IImageProfileManager.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/IImageProfileManager.cs @@ -8,8 +8,5 @@ public interface IImageProfileManager : IDependency { string GetImageProfileUrl(string path, string profileName, FilterRecord customFilter); string GetImageProfileUrl(string path, string profileName, FilterRecord customFilter, ContentItem contentItem); string GetImageProfileUrl(string path, string profileName, ContentItem contentItem, params FilterRecord[] customFilters); - - bool PurgeImageProfile(int id); - bool PurgeObsoleteImageProfiles(); } } diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/IImageProfileService.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/IImageProfileService.cs index e3a240f75f0..d8c77cc66bc 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/IImageProfileService.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/IImageProfileService.cs @@ -10,5 +10,12 @@ public interface IImageProfileService : IDependency { void DeleteImageProfile(int id); void MoveUp(int filterId); void MoveDown(int filterId); + bool PurgeImageProfile(int id); + bool PurgeObsoleteImageProfiles(); } -} \ No newline at end of file + + public static class ImageProfileServiceExtensions { + public static string GetNameHashCode(this IImageProfileService service, string name) => + name.GetHashCode().ToString("x").ToLowerInvariant(); + } +} diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs index a20b016db29..44bacde9257 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs @@ -4,9 +4,9 @@ using System.IO; using System.Linq; using System.Net; -using System.Security.Cryptography; using System.Web; using Orchard.ContentManagement; +using Orchard.Environment.Configuration; using Orchard.FileSystems.Media; using Orchard.Forms.Services; using Orchard.Logging; @@ -15,8 +15,6 @@ using Orchard.MediaProcessing.Models; using Orchard.Tokens; using Orchard.Utility.Extensions; -using Orchard.Environment.Configuration; -using Orchard.Caching; namespace Orchard.MediaProcessing.Services { public class ImageProfileManager : IImageProfileManager { @@ -26,7 +24,6 @@ public class ImageProfileManager : IImageProfileManager { private readonly IImageProcessingManager _processingManager; private readonly IOrchardServices _services; private readonly ITokenizer _tokenizer; - private readonly ISignals _signals; private readonly IAppConfigurationAccessor _appConfigurationAccessor; public ImageProfileManager( @@ -36,7 +33,6 @@ public ImageProfileManager( IImageProcessingManager processingManager, IOrchardServices services, ITokenizer tokenizer, - ISignals signals, IAppConfigurationAccessor appConfigurationAccessor) { _storageProvider = storageProvider; _fileNameProvider = fileNameProvider; @@ -44,7 +40,6 @@ public ImageProfileManager( _processingManager = processingManager; _services = services; _tokenizer = tokenizer; - _signals = signals; _appConfigurationAccessor = appConfigurationAccessor; Logger = NullLogger.Instance; @@ -52,7 +47,7 @@ public ImageProfileManager( public ILogger Logger { get; set; } - public string GetImageProfileUrl(string path, string profileName) { + public string GetImageProfileUrl(string path, string profileName) { return GetImageProfileUrl(path, profileName, null, new FilterRecord[] { }); } @@ -74,19 +69,19 @@ public string GetImageProfileUrl(string path, string profileName, ContentItem co // path is the publicUrl of the media, so it might contain url-encoded chars // try to load the processed filename from cache - var filePath = _fileNameProvider.GetFileName(profileName, System.Web.HttpUtility.UrlDecode(path)); + var filePath = _fileNameProvider.GetFileName(profileName, HttpUtility.UrlDecode(path)); bool process = false; //after reboot the app cache is empty so we reload the image in the cache if it exists in the _Profiles folder if (string.IsNullOrEmpty(filePath)) { - var profileFilePath = _storageProvider.Combine("_Profiles", FormatProfilePath(profileName, System.Web.HttpUtility.UrlDecode(path))); + var profileFilePath = _storageProvider.Combine("_Profiles", FormatProfilePath(profileName, HttpUtility.UrlDecode(path))); if (_storageProvider.FileExists(profileFilePath)) { - _fileNameProvider.UpdateFileName(profileName, System.Web.HttpUtility.UrlDecode(path), profileFilePath); + _fileNameProvider.UpdateFileName(profileName, HttpUtility.UrlDecode(path), profileFilePath); filePath = profileFilePath; } } - + // 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); @@ -94,7 +89,7 @@ public string GetImageProfileUrl(string path, string profileName, ContentItem co process = true; } - // the processd file doesn't exist anymore, process it + // 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); @@ -129,7 +124,7 @@ public string GetImageProfileUrl(string path, string profileName, ContentItem co else { profilePart = _services.ContentManager.New("ImageProfile"); profilePart.Name = profileName; - foreach (var customFilter in customFilters) { + foreach (var customFilter in customFilters) { profilePart.Filters.Add(customFilter); } } @@ -143,7 +138,7 @@ public string GetImageProfileUrl(string path, string profileName, ContentItem co return null; } - var filterContext = new FilterContext { Media = image, FilePath = _storageProvider.Combine("_Profiles", FormatProfilePath(profileName, System.Web.HttpUtility.UrlDecode(path))) }; + var filterContext = new FilterContext { Media = image, FilePath = _storageProvider.Combine("_Profiles", FormatProfilePath(profileName, HttpUtility.UrlDecode(path))) }; var tokens = new Dictionary(); // if a content item is provided, use it while tokenizing @@ -161,7 +156,7 @@ public string GetImageProfileUrl(string path, string profileName, ContentItem co descriptor.Filter(filterContext); } - _fileNameProvider.UpdateFileName(profileName, System.Web.HttpUtility.UrlDecode(path), filterContext.FilePath); + _fileNameProvider.UpdateFileName(profileName, HttpUtility.UrlDecode(path), filterContext.FilePath); if (!filterContext.Saved) { try { @@ -183,7 +178,7 @@ public string GetImageProfileUrl(string path, string profileName, ContentItem co filterContext.FilePath = newFile.GetPath(); } } - catch(Exception e) { + catch (Exception e) { Logger.Error(e, "A profile could not be processed: " + path); } } @@ -212,7 +207,7 @@ private Stream GetImage(string path) { var file = _storageProvider.GetFile(storagePath); return file.OpenRead(); } - catch(Exception e) { + catch (Exception e) { Logger.Error(e, "path:" + path + " storagePath:" + storagePath); } } @@ -244,54 +239,16 @@ private bool TryGetImageLastUpdated(string path, out DateTime lastUpdated) { return false; } - public bool PurgeImageProfile(int id) { - var profile = _profileService.GetImageProfile(id); - try { - var folder = _storageProvider.Combine("_Profiles", GetHexHashCode(profile.Name)); - _storageProvider.DeleteFolder(folder); - profile.FileNames.Clear(); - _signals.Trigger("MediaProcessing_Saved_" + profile.Name); - return true; - } - catch (Exception ex) { - Logger.Warning(ex, "Unable to purge image profile '{0}'", profile.Name); - return false; - } - } - - public bool PurgeObsoleteImageProfiles() { - var profiles = _profileService.GetAllImageProfiles(); - try { - if (profiles != null) { - var validPaths = profiles.Select(profile => _storageProvider.Combine("_Profiles", GetHexHashCode(profile.Name))); - foreach (var folder in _storageProvider.ListFolders("_Profiles").Select(f => f.GetPath())) { - if (!validPaths.Any(v => folder.StartsWith(v))) { - _storageProvider.DeleteFolder(folder); - } - } - } - return true; - } - catch (Exception ex) { - Logger.Warning(ex, "Unable to purge obsolete image profiles"); - return false; - } - } - private string FormatProfilePath(string profileName, string path) { var normalizedPath = ShouldNormalizePath() ? NormalizePath(path) : path; var filenameWithExtension = Path.GetFileName(normalizedPath) ?? ""; var fileLocation = normalizedPath.Substring(0, normalizedPath.Length - filenameWithExtension.Length); return _storageProvider.Combine( - _storageProvider.Combine(GetHexHashCode(profileName), GetHexHashCode(fileLocation)), + _storageProvider.Combine(_profileService.GetNameHashCode(profileName), _profileService.GetNameHashCode(fileLocation)), filenameWithExtension); } - private string GetHexHashCode(string value) { - return value.GetHashCode().ToString("x").ToLowerInvariant(); - } - private string NormalizePath(string path) { //Slice at the protocol, if any. E.g. http:// or https://. var index = path.IndexOf("//", StringComparison.OrdinalIgnoreCase); diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileService.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileService.cs index 27e34228cdf..c1266070ab2 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileService.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileService.cs @@ -1,31 +1,34 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Orchard.Caching; using Orchard.ContentManagement; using Orchard.Data; -using Orchard.Localization; +using Orchard.FileSystems.Media; +using Orchard.Logging; using Orchard.MediaProcessing.Models; namespace Orchard.MediaProcessing.Services { - public class ImageProfileService : IImageProfileService { + public class ImageProfileService : Component, IImageProfileService { private readonly IContentManager _contentManager; private readonly ICacheManager _cacheManager; private readonly IRepository _filterRepository; private readonly ISignals _signals; + private readonly IStorageProvider _storageProvider; public ImageProfileService( - IContentManager contentManager, + IContentManager contentManager, ICacheManager cacheManager, IRepository filterRepository, - ISignals signals) { + ISignals signals, + IStorageProvider storageProvider) { _contentManager = contentManager; _cacheManager = cacheManager; _filterRepository = filterRepository; _signals = signals; + _storageProvider = storageProvider; } - public Localizer T { get; set; } - public ImageProfilePart GetImageProfile(int id) { return _contentManager.Get(id); } @@ -115,5 +118,39 @@ public void MoveDown(int filterId) { next.Position = filter.Position; filter.Position = temp; } + + public bool PurgeImageProfile(int id) { + var profile = GetImageProfile(id); + try { + var folder = _storageProvider.Combine("_Profiles", this.GetNameHashCode(profile.Name)); + _storageProvider.DeleteFolder(folder); + profile.FileNames.Clear(); + _signals.Trigger("MediaProcessing_Saved_" + profile.Name); + return true; + } + catch (Exception ex) { + Logger.Warning(ex, "Unable to purge image profile '{0}'", profile.Name); + return false; + } + } + + public bool PurgeObsoleteImageProfiles() { + var profiles = GetAllImageProfiles(); + try { + if (profiles != null) { + var validPaths = profiles.Select(profile => _storageProvider.Combine("_Profiles", this.GetNameHashCode(profile.Name))); + foreach (var folder in _storageProvider.ListFolders("_Profiles").Select(f => f.GetPath())) { + if (!validPaths.Any(folder.StartsWith)) { + _storageProvider.DeleteFolder(folder); + } + } + } + return true; + } + catch (Exception ex) { + Logger.Warning(ex, "Unable to purge obsolete image profiles"); + return false; + } + } } -} \ No newline at end of file +} From a640ca05196983949d23b593342629b3c02588ef Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Tue, 9 Apr 2024 18:24:25 +0200 Subject: [PATCH 3/7] Deleting an Image Profile now also removes all its files too --- .../Controllers/AdminController.cs | 2 +- .../Services/ImageProfileService.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Controllers/AdminController.cs index 4d350f15d79..ad6ab16f245 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Controllers/AdminController.cs @@ -154,7 +154,7 @@ public ActionResult Delete(int id) { return HttpNotFound(); } - Services.ContentManager.Remove(profile.ContentItem); + _profileService.DeleteImageProfile(id); Services.Notifier.Success(T("Image Profile {0} deleted", profile.Name)); return RedirectToAction("Index"); diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileService.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileService.cs index c1266070ab2..20853b6d376 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileService.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileService.cs @@ -73,6 +73,7 @@ public void DeleteImageProfile(int id) { var profile = _contentManager.Get(id); if (profile != null) { + DeleteImageProfileFolder(profile.As().Name); _contentManager.Remove(profile); } } @@ -122,8 +123,7 @@ public void MoveDown(int filterId) { public bool PurgeImageProfile(int id) { var profile = GetImageProfile(id); try { - var folder = _storageProvider.Combine("_Profiles", this.GetNameHashCode(profile.Name)); - _storageProvider.DeleteFolder(folder); + DeleteImageProfileFolder(profile.Name); profile.FileNames.Clear(); _signals.Trigger("MediaProcessing_Saved_" + profile.Name); return true; @@ -152,5 +152,10 @@ public bool PurgeObsoleteImageProfiles() { return false; } } + + private void DeleteImageProfileFolder(string profileName) { + var folder = _storageProvider.Combine("_Profiles", this.GetNameHashCode(profileName)); + _storageProvider.DeleteFolder(folder); + } } } From 4484f8255ef7cb8fdba38083dabe9ed067c4bb84 Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Tue, 9 Apr 2024 18:27:44 +0200 Subject: [PATCH 4/7] Comment formatting --- .../Services/ImageProfileManager.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs index 44bacde9257..511bc3681bf 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs @@ -250,11 +250,12 @@ private string FormatProfilePath(string profileName, string path) { } private string NormalizePath(string path) { - //Slice at the protocol, if any. E.g. http:// or https://. + // Slice at the protocol, if any, e.g. "http://" or "https://". var index = path.IndexOf("//", StringComparison.OrdinalIgnoreCase); - //Slice at the first directory after the protocol or at 0 if no protocol specified. + // Slice at the first directory after the protocol or at 0 if no protocol specified. index = path.IndexOf("/", index < 0 ? 0 : index + 2, StringComparison.OrdinalIgnoreCase); - //Return path from the first directory, replacing lcase 'media' (Azure container) with ucase 'Media' (filestorage). + // Return path from the first directory, replacing lowercase 'media' (Azure container) with uppercase + // 'Media' (filestorage). return path.Substring(index < 1 ? 0 : index).Replace("media", "Media"); } From 0afdb0f5bb1b43723b88dbb9aea50e41b496c5dd Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Tue, 9 Apr 2024 18:52:20 +0200 Subject: [PATCH 5/7] Caching the value of the "Orchard.MediaProcessing.NormalizePath" app setting in ImageProfileManager --- .../Services/ImageProfileManager.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs index 511bc3681bf..613e940f541 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Web; +using Orchard.Caching; using Orchard.ContentManagement; using Orchard.Environment.Configuration; using Orchard.FileSystems.Media; @@ -25,6 +26,7 @@ public class ImageProfileManager : IImageProfileManager { private readonly IOrchardServices _services; private readonly ITokenizer _tokenizer; private readonly IAppConfigurationAccessor _appConfigurationAccessor; + private readonly ICacheManager _cacheManager; public ImageProfileManager( IStorageProvider storageProvider, @@ -33,7 +35,8 @@ public ImageProfileManager( IImageProcessingManager processingManager, IOrchardServices services, ITokenizer tokenizer, - IAppConfigurationAccessor appConfigurationAccessor) { + IAppConfigurationAccessor appConfigurationAccessor, + ICacheManager cacheManager) { _storageProvider = storageProvider; _fileNameProvider = fileNameProvider; _profileService = profileService; @@ -41,6 +44,7 @@ public ImageProfileManager( _services = services; _tokenizer = tokenizer; _appConfigurationAccessor = appConfigurationAccessor; + _cacheManager = cacheManager; Logger = NullLogger.Instance; } @@ -259,9 +263,10 @@ private string NormalizePath(string path) { return path.Substring(index < 1 ? 0 : index).Replace("media", "Media"); } - private bool ShouldNormalizePath() { - var normalizePath = _appConfigurationAccessor.GetConfiguration("Orchard.MediaProcessing.NormalizePath"); - return string.IsNullOrEmpty(normalizePath) || normalizePath.Equals("true", StringComparison.OrdinalIgnoreCase); - } + private bool ShouldNormalizePath() => + _cacheManager.Get("MediaProcessing.NormalizePath", true, ctx => { + var normalizePath = _appConfigurationAccessor.GetConfiguration("Orchard.MediaProcessing.NormalizePath"); + return string.IsNullOrEmpty(normalizePath) || normalizePath.Equals("true", StringComparison.OrdinalIgnoreCase); + }); } } From 42b5bd2cfc32f9a0e1fba060e6a868ec3cd504f5 Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Wed, 17 Apr 2024 16:23:13 +0200 Subject: [PATCH 6/7] Code styling in ImageProfileManager --- .../Orchard.MediaProcessing/Services/ImageProfileManager.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs index 613e940f541..49c41fd1f2b 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs @@ -92,14 +92,12 @@ public string GetImageProfileUrl(string path, string profileName, ContentItem co 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; @@ -250,7 +248,7 @@ private string FormatProfilePath(string profileName, string path) { return _storageProvider.Combine( _storageProvider.Combine(_profileService.GetNameHashCode(profileName), _profileService.GetNameHashCode(fileLocation)), - filenameWithExtension); + filenameWithExtension); } private string NormalizePath(string path) { From 03f775d474fbc33fe4486f2a63b2add209b120a9 Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Wed, 17 Apr 2024 17:25:17 +0200 Subject: [PATCH 7/7] Formatting and code styling ImageProfileManager --- .../Services/ImageProfileManager.cs | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs index 8d939a524ac..68d4cf4a8e4 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaProcessing/Services/ImageProfileManager.cs @@ -66,7 +66,7 @@ public string GetImageProfileUrl(string path, string profileName, ContentItem co // path is the publicUrl of the media, so it might contain url-encoded chars // try to load the processed filename from cache - var filePath = _fileNameProvider.GetFileName(profileName, System.Web.HttpUtility.UrlDecode(path)); + var filePath = _fileNameProvider.GetFileName(profileName, HttpUtility.UrlDecode(path)); bool process = false; // Before checking everything else, ensure that the content item that needs to be processed has a ImagePart. @@ -79,10 +79,10 @@ public string GetImageProfileUrl(string path, string profileName, ContentItem co if (checkForProfile) { //after reboot the app cache is empty so we reload the image in the cache if it exists in the _Profiles folder if (string.IsNullOrEmpty(filePath)) { - var profileFilePath = _storageProvider.Combine("_Profiles", FormatProfilePath(profileName, System.Web.HttpUtility.UrlDecode(path))); + var profileFilePath = _storageProvider.Combine("_Profiles", FormatProfilePath(profileName, HttpUtility.UrlDecode(path))); if (_storageProvider.FileExists(profileFilePath)) { - _fileNameProvider.UpdateFileName(profileName, System.Web.HttpUtility.UrlDecode(path), profileFilePath); + _fileNameProvider.UpdateFileName(profileName, HttpUtility.UrlDecode(path), profileFilePath); filePath = profileFilePath; } } @@ -93,28 +93,24 @@ public string GetImageProfileUrl(string path, string profileName, ContentItem co 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(); + else if (TryGetImageLastUpdated(path, out DateTime 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); + if (pathLastUpdated > filePathLastUpdated) { + Logger.Debug("Original file more recent, processing required, profile {0} for image {1}", profileName, path); - process = true; - } + process = true; } } - } else { + } + else { // Since media with no ImagePart have no profile, filePath is null, so it's set again to its original path on the storage provider. if (string.IsNullOrWhiteSpace(filePath)) { filePath = _storageProvider.GetStoragePath(path); @@ -129,9 +125,11 @@ public string GetImageProfileUrl(string path, string profileName, ContentItem co if (customFilters == null || !customFilters.Any(c => c != null)) { profilePart = _profileService.GetImageProfileByName(profileName); - if (profilePart == null) - return String.Empty; - } else { + if (profilePart == null) { + return string.Empty; + } + } + else { profilePart = _services.ContentManager.New("ImageProfile"); profilePart.Name = profileName; foreach (var customFilter in customFilters) { @@ -142,13 +140,13 @@ public string GetImageProfileUrl(string path, string profileName, ContentItem co // 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 - lock (String.Intern(path)) { + lock (string.Intern(path)) { using (var image = GetImage(path)) { if (image == null) { return null; } - var filterContext = new FilterContext { Media = image, FilePath = _storageProvider.Combine("_Profiles", FormatProfilePath(profileName, System.Web.HttpUtility.UrlDecode(path))) }; + var filterContext = new FilterContext { Media = image, FilePath = _storageProvider.Combine("_Profiles", FormatProfilePath(profileName, HttpUtility.UrlDecode(path))) }; var tokens = new Dictionary(); // if a content item is provided, use it while tokenizing @@ -166,7 +164,7 @@ public string GetImageProfileUrl(string path, string profileName, ContentItem co descriptor.Filter(filterContext); } - _fileNameProvider.UpdateFileName(profileName, System.Web.HttpUtility.UrlDecode(path), filterContext.FilePath); + _fileNameProvider.UpdateFileName(profileName, HttpUtility.UrlDecode(path), filterContext.FilePath); if (!filterContext.Saved) { try { @@ -187,7 +185,8 @@ public string GetImageProfileUrl(string path, string profileName, ContentItem co // the storage provider may have altered the filepath filterContext.FilePath = newFile.GetPath(); } - } catch (Exception e) { + } + catch (Exception e) { Logger.Error(e, "A profile could not be processed: " + path); } } @@ -215,14 +214,14 @@ private Stream GetImage(string path) { try { var file = _storageProvider.GetFile(storagePath); return file.OpenRead(); - } catch (Exception e) { + } + catch (Exception e) { Logger.Error(e, "path:" + path + " storagePath:" + storagePath); } } // http://blob.storage-provider.net/my-image.jpg - Uri absoluteUri; - if (Uri.TryCreate(path, UriKind.Absolute, out absoluteUri)) { + if (Uri.TryCreate(path, UriKind.Absolute, out Uri absoluteUri)) { return new WebClient().OpenRead(absoluteUri); } @@ -248,13 +247,12 @@ private bool TryGetImageLastUpdated(string path, out DateTime lastUpdated) { } private string FormatProfilePath(string profileName, string path) { - var filenameWithExtension = Path.GetFileName(path) ?? ""; var fileLocation = path.Substring(0, path.Length - filenameWithExtension.Length); return _storageProvider.Combine( - _storageProvider.Combine(profileName.GetHashCode().ToString("x").ToLowerInvariant(), fileLocation.GetHashCode().ToString("x").ToLowerInvariant()), - filenameWithExtension); + _storageProvider.Combine(_profileService.GetNameHashCode(profileName), _profileService.GetNameHashCode(fileLocation)), + filenameWithExtension); } } }