Skip to content

Commit

Permalink
#6981: Normalized image profile path hash and added profile purging (#…
Browse files Browse the repository at this point in the history
…8788)

* #6981 Normalized image profile path hash and added profile purging

* Profile purge functions rather belong in IImageProfileService

* Deleting an Image Profile now also removes all its files too

* Comment formatting

* Caching the value of the "Orchard.MediaProcessing.NormalizePath" app setting in ImageProfileManager

* Code styling in ImageProfileManager

* Formatting and code styling ImageProfileManager

---------

Co-authored-by: Arjan Noordende <[email protected]>
  • Loading branch information
BenedekFarkas and anoordende authored May 3, 2024
1 parent 6eab0a1 commit 62038ed
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,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<ImageProfileEntry>(), Options = new AdminIndexOptions()};
var viewModel = new AdminIndexViewModel { ImageProfiles = new List<ImageProfileEntry>(), Options = new AdminIndexOptions() };
UpdateModel(viewModel);

var checkedItems = viewModel.ImageProfiles.Where(c => c.IsChecked);
Expand Down Expand Up @@ -133,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
});
}
}
Expand All @@ -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");
Expand All @@ -175,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) {
Expand All @@ -185,6 +185,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 (_profileService.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 (_profileService.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>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) {
return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ public IList<FileNameRecord> FileNames {
get { return Record.FileNames; }
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

public static class ImageProfileServiceExtensions {
public static string GetNameHashCode(this IImageProfileService service, string name) =>
name.GetHashCode().ToString("x").ToLowerInvariant();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
}
}
Expand All @@ -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);
Expand All @@ -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<ImageProfilePart>("ImageProfile");
profilePart.Name = profileName;
foreach (var customFilter in customFilters) {
Expand All @@ -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<string, object>();
// if a content item is provided, use it while tokenizing
Expand All @@ -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 {
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<FilterRecord> _filterRepository;
private readonly ISignals _signals;
private readonly IStorageProvider _storageProvider;

public ImageProfileService(
IContentManager contentManager,
IContentManager contentManager,
ICacheManager cacheManager,
IRepository<FilterRecord> 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<ImageProfilePart>(id);
}
Expand Down Expand Up @@ -70,6 +73,7 @@ public void DeleteImageProfile(int id) {
var profile = _contentManager.Get(id);

if (profile != null) {
DeleteImageProfileFolder(profile.As<ImageProfilePart>().Name);
_contentManager.Remove(profile);
}
}
Expand Down Expand Up @@ -115,5 +119,43 @@ public void MoveDown(int filterId) {
next.Position = filter.Position;
filter.Position = temp;
}

public bool PurgeImageProfile(int id) {
var profile = GetImageProfile(id);
try {
DeleteImageProfileFolder(profile.Name);
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;
}
}

private void DeleteImageProfileFolder(string profileName) {
var folder = _storageProvider.Combine("_Profiles", this.GetNameHashCode(profileName));
_storageProvider.DeleteFolder(folder);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@

@using (Html.BeginFormAntiForgeryPost()) {
@Html.ValidationSummary()
<div class="manage">@Html.ActionLink(T("Add a new Media Profile").ToString(), "Create", new { Area = "Contents", id = "ImageProfile", returnurl = HttpContext.Current.Request.RawUrl }, new { @class = "button primaryAction" })</div>
<div class="manage">
@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" })
</div>

<fieldset class="bulk-actions">
<label for="publishActions">@T("Actions:")</label>
Expand Down Expand Up @@ -56,6 +59,7 @@
<td>
@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 })*@
</td>
Expand Down

0 comments on commit 62038ed

Please sign in to comment.