mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-07-15 23:37:44 +08:00
* #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 <arjan@zumey.com>
This commit is contained in:
parent
6eab0a1260
commit
62038ed1bb
@ -154,7 +154,7 @@ namespace Orchard.MediaProcessing.Controllers {
|
|||||||
return HttpNotFound();
|
return HttpNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
Services.ContentManager.Remove(profile.ContentItem);
|
_profileService.DeleteImageProfile(id);
|
||||||
Services.Notifier.Success(T("Image Profile {0} deleted", profile.Name));
|
Services.Notifier.Success(T("Image Profile {0} deleted", profile.Name));
|
||||||
|
|
||||||
return RedirectToAction("Index");
|
return RedirectToAction("Index");
|
||||||
@ -185,6 +185,36 @@ namespace Orchard.MediaProcessing.Controllers {
|
|||||||
throw new NotImplementedException();
|
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) {
|
bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) {
|
||||||
return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
|
return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
|
||||||
}
|
}
|
||||||
|
@ -10,5 +10,12 @@ namespace Orchard.MediaProcessing.Services {
|
|||||||
void DeleteImageProfile(int id);
|
void DeleteImageProfile(int id);
|
||||||
void MoveUp(int filterId);
|
void MoveUp(int filterId);
|
||||||
void MoveDown(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();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -66,7 +66,7 @@ namespace Orchard.MediaProcessing.Services {
|
|||||||
// path is the publicUrl of the media, so it might contain url-encoded chars
|
// path is the publicUrl of the media, so it might contain url-encoded chars
|
||||||
|
|
||||||
// try to load the processed filename from cache
|
// 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;
|
bool process = false;
|
||||||
|
|
||||||
// Before checking everything else, ensure that the content item that needs to be processed has a ImagePart.
|
// Before checking everything else, ensure that the content item that needs to be processed has a ImagePart.
|
||||||
@ -79,10 +79,10 @@ namespace Orchard.MediaProcessing.Services {
|
|||||||
if (checkForProfile) {
|
if (checkForProfile) {
|
||||||
//after reboot the app cache is empty so we reload the image in the cache if it exists in the _Profiles folder
|
//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)) {
|
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)) {
|
if (_storageProvider.FileExists(profileFilePath)) {
|
||||||
_fileNameProvider.UpdateFileName(profileName, System.Web.HttpUtility.UrlDecode(path), profileFilePath);
|
_fileNameProvider.UpdateFileName(profileName, HttpUtility.UrlDecode(path), profileFilePath);
|
||||||
filePath = profileFilePath;
|
filePath = profileFilePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,18 +93,14 @@ namespace Orchard.MediaProcessing.Services {
|
|||||||
|
|
||||||
process = true;
|
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)) {
|
else if (!_storageProvider.FileExists(filePath)) {
|
||||||
Logger.Debug("Processed file no longer exists, processing required, profile {0} for image {1}", profileName, path);
|
Logger.Debug("Processed file no longer exists, processing required, profile {0} for image {1}", profileName, path);
|
||||||
|
|
||||||
process = true;
|
process = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the original file is more recent, process it
|
// if the original file is more recent, process it
|
||||||
else {
|
else if (TryGetImageLastUpdated(path, out DateTime pathLastUpdated)) {
|
||||||
DateTime pathLastUpdated;
|
|
||||||
if (TryGetImageLastUpdated(path, out pathLastUpdated)) {
|
|
||||||
var filePathLastUpdated = _storageProvider.GetFile(filePath).GetLastUpdated();
|
var filePathLastUpdated = _storageProvider.GetFile(filePath).GetLastUpdated();
|
||||||
|
|
||||||
if (pathLastUpdated > filePathLastUpdated) {
|
if (pathLastUpdated > filePathLastUpdated) {
|
||||||
@ -114,7 +110,7 @@ namespace Orchard.MediaProcessing.Services {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} 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.
|
// 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)) {
|
if (string.IsNullOrWhiteSpace(filePath)) {
|
||||||
filePath = _storageProvider.GetStoragePath(path);
|
filePath = _storageProvider.GetStoragePath(path);
|
||||||
@ -129,9 +125,11 @@ namespace Orchard.MediaProcessing.Services {
|
|||||||
|
|
||||||
if (customFilters == null || !customFilters.Any(c => c != null)) {
|
if (customFilters == null || !customFilters.Any(c => c != null)) {
|
||||||
profilePart = _profileService.GetImageProfileByName(profileName);
|
profilePart = _profileService.GetImageProfileByName(profileName);
|
||||||
if (profilePart == null)
|
if (profilePart == null) {
|
||||||
return String.Empty;
|
return string.Empty;
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
profilePart = _services.ContentManager.New<ImageProfilePart>("ImageProfile");
|
profilePart = _services.ContentManager.New<ImageProfilePart>("ImageProfile");
|
||||||
profilePart.Name = profileName;
|
profilePart.Name = profileName;
|
||||||
foreach (var customFilter in customFilters) {
|
foreach (var customFilter in customFilters) {
|
||||||
@ -142,13 +140,13 @@ namespace Orchard.MediaProcessing.Services {
|
|||||||
// prevent two requests from processing the same file at the same time
|
// 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
|
// this is only thread safe at the machine level, so there is a try/catch later
|
||||||
// to handle cross machines concurrency
|
// to handle cross machines concurrency
|
||||||
lock (String.Intern(path)) {
|
lock (string.Intern(path)) {
|
||||||
using (var image = GetImage(path)) {
|
using (var image = GetImage(path)) {
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
return 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>();
|
var tokens = new Dictionary<string, object>();
|
||||||
// if a content item is provided, use it while tokenizing
|
// if a content item is provided, use it while tokenizing
|
||||||
@ -166,7 +164,7 @@ namespace Orchard.MediaProcessing.Services {
|
|||||||
descriptor.Filter(filterContext);
|
descriptor.Filter(filterContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
_fileNameProvider.UpdateFileName(profileName, System.Web.HttpUtility.UrlDecode(path), filterContext.FilePath);
|
_fileNameProvider.UpdateFileName(profileName, HttpUtility.UrlDecode(path), filterContext.FilePath);
|
||||||
|
|
||||||
if (!filterContext.Saved) {
|
if (!filterContext.Saved) {
|
||||||
try {
|
try {
|
||||||
@ -187,7 +185,8 @@ namespace Orchard.MediaProcessing.Services {
|
|||||||
// the storage provider may have altered the filepath
|
// the storage provider may have altered the filepath
|
||||||
filterContext.FilePath = newFile.GetPath();
|
filterContext.FilePath = newFile.GetPath();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
}
|
||||||
|
catch (Exception e) {
|
||||||
Logger.Error(e, "A profile could not be processed: " + path);
|
Logger.Error(e, "A profile could not be processed: " + path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -215,14 +214,14 @@ namespace Orchard.MediaProcessing.Services {
|
|||||||
try {
|
try {
|
||||||
var file = _storageProvider.GetFile(storagePath);
|
var file = _storageProvider.GetFile(storagePath);
|
||||||
return file.OpenRead();
|
return file.OpenRead();
|
||||||
} catch (Exception e) {
|
}
|
||||||
|
catch (Exception e) {
|
||||||
Logger.Error(e, "path:" + path + " storagePath:" + storagePath);
|
Logger.Error(e, "path:" + path + " storagePath:" + storagePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://blob.storage-provider.net/my-image.jpg
|
// http://blob.storage-provider.net/my-image.jpg
|
||||||
Uri absoluteUri;
|
if (Uri.TryCreate(path, UriKind.Absolute, out Uri absoluteUri)) {
|
||||||
if (Uri.TryCreate(path, UriKind.Absolute, out absoluteUri)) {
|
|
||||||
return new WebClient().OpenRead(absoluteUri);
|
return new WebClient().OpenRead(absoluteUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,12 +247,11 @@ namespace Orchard.MediaProcessing.Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private string FormatProfilePath(string profileName, string path) {
|
private string FormatProfilePath(string profileName, string path) {
|
||||||
|
|
||||||
var filenameWithExtension = Path.GetFileName(path) ?? "";
|
var filenameWithExtension = Path.GetFileName(path) ?? "";
|
||||||
var fileLocation = path.Substring(0, path.Length - filenameWithExtension.Length);
|
var fileLocation = path.Substring(0, path.Length - filenameWithExtension.Length);
|
||||||
|
|
||||||
return _storageProvider.Combine(
|
return _storageProvider.Combine(
|
||||||
_storageProvider.Combine(profileName.GetHashCode().ToString("x").ToLowerInvariant(), fileLocation.GetHashCode().ToString("x").ToLowerInvariant()),
|
_storageProvider.Combine(_profileService.GetNameHashCode(profileName), _profileService.GetNameHashCode(fileLocation)),
|
||||||
filenameWithExtension);
|
filenameWithExtension);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,34 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Orchard.Caching;
|
using Orchard.Caching;
|
||||||
using Orchard.ContentManagement;
|
using Orchard.ContentManagement;
|
||||||
using Orchard.Data;
|
using Orchard.Data;
|
||||||
using Orchard.Localization;
|
using Orchard.FileSystems.Media;
|
||||||
|
using Orchard.Logging;
|
||||||
using Orchard.MediaProcessing.Models;
|
using Orchard.MediaProcessing.Models;
|
||||||
|
|
||||||
namespace Orchard.MediaProcessing.Services {
|
namespace Orchard.MediaProcessing.Services {
|
||||||
public class ImageProfileService : IImageProfileService {
|
public class ImageProfileService : Component, IImageProfileService {
|
||||||
private readonly IContentManager _contentManager;
|
private readonly IContentManager _contentManager;
|
||||||
private readonly ICacheManager _cacheManager;
|
private readonly ICacheManager _cacheManager;
|
||||||
private readonly IRepository<FilterRecord> _filterRepository;
|
private readonly IRepository<FilterRecord> _filterRepository;
|
||||||
private readonly ISignals _signals;
|
private readonly ISignals _signals;
|
||||||
|
private readonly IStorageProvider _storageProvider;
|
||||||
|
|
||||||
public ImageProfileService(
|
public ImageProfileService(
|
||||||
IContentManager contentManager,
|
IContentManager contentManager,
|
||||||
ICacheManager cacheManager,
|
ICacheManager cacheManager,
|
||||||
IRepository<FilterRecord> filterRepository,
|
IRepository<FilterRecord> filterRepository,
|
||||||
ISignals signals) {
|
ISignals signals,
|
||||||
|
IStorageProvider storageProvider) {
|
||||||
_contentManager = contentManager;
|
_contentManager = contentManager;
|
||||||
_cacheManager = cacheManager;
|
_cacheManager = cacheManager;
|
||||||
_filterRepository = filterRepository;
|
_filterRepository = filterRepository;
|
||||||
_signals = signals;
|
_signals = signals;
|
||||||
|
_storageProvider = storageProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Localizer T { get; set; }
|
|
||||||
|
|
||||||
public ImageProfilePart GetImageProfile(int id) {
|
public ImageProfilePart GetImageProfile(int id) {
|
||||||
return _contentManager.Get<ImageProfilePart>(id);
|
return _contentManager.Get<ImageProfilePart>(id);
|
||||||
}
|
}
|
||||||
@ -70,6 +73,7 @@ namespace Orchard.MediaProcessing.Services {
|
|||||||
var profile = _contentManager.Get(id);
|
var profile = _contentManager.Get(id);
|
||||||
|
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
|
DeleteImageProfileFolder(profile.As<ImageProfilePart>().Name);
|
||||||
_contentManager.Remove(profile);
|
_contentManager.Remove(profile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,5 +119,43 @@ namespace Orchard.MediaProcessing.Services {
|
|||||||
next.Position = filter.Position;
|
next.Position = filter.Position;
|
||||||
filter.Position = temp;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,7 +14,10 @@
|
|||||||
|
|
||||||
@using (Html.BeginFormAntiForgeryPost()) {
|
@using (Html.BeginFormAntiForgeryPost()) {
|
||||||
@Html.ValidationSummary()
|
@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">
|
<fieldset class="bulk-actions">
|
||||||
<label for="publishActions">@T("Actions:")</label>
|
<label for="publishActions">@T("Actions:")</label>
|
||||||
@ -56,6 +59,7 @@
|
|||||||
<td>
|
<td>
|
||||||
@Html.ActionLink(T("Properties").ToString(), "Edit", new { Area = "Contents", id = entry.ImageProfileId, returnurl = HttpContext.Current.Request.RawUrl }) |
|
@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("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("Delete").ToString(), "Delete", new { id = entry.ImageProfileId }, new { itemprop = "RemoveUrl UnsafeUrl" })
|
||||||
@*@Html.ActionLink(T("Preview").ToString(), "Preview", new { id = entry.ImageProfileId })*@
|
@*@Html.ActionLink(T("Preview").ToString(), "Preview", new { id = entry.ImageProfileId })*@
|
||||||
</td>
|
</td>
|
||||||
|
Loading…
Reference in New Issue
Block a user