Fixes: U4-4746 Examine does not index updated paths/levels for moved/trashed media, U4-4744 Examine events do not listen for recycle bin emptying
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Events;
|
||||
@@ -123,23 +124,22 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
//Bind to media events
|
||||
|
||||
MediaService.Saved += MediaServiceSaved;
|
||||
//We need to perform all of the 'before' events here because we need a reference to the
|
||||
//media item's Path before it is moved/deleting/trashed
|
||||
//see: http://issues.umbraco.org/issue/U4-1653
|
||||
MediaService.Deleting += MediaServiceDeleting;
|
||||
MediaService.Moving += MediaServiceMoving;
|
||||
MediaService.Trashing += MediaServiceTrashing;
|
||||
MediaService.Saved += MediaServiceSaved;
|
||||
MediaService.Deleted += MediaServiceDeleted;
|
||||
MediaService.Moved += MediaServiceMoved;
|
||||
MediaService.Trashed += MediaServiceTrashed;
|
||||
MediaService.EmptiedRecycleBin += MediaServiceEmptiedRecycleBin;
|
||||
|
||||
//Bind to content events - this is for unpublished content syncing across servers (primarily for examine)
|
||||
|
||||
ContentService.EmptiedRecycleBin += ContentServiceEmptiedRecycleBin;
|
||||
ContentService.Saved += ContentServiceSaved;
|
||||
ContentService.Deleted += ContentServiceDeleted;
|
||||
ContentService.Copied += ContentServiceCopied;
|
||||
//NOTE: we do not listen for the trashed event because there is no cache to update for content in that case since
|
||||
// the unpublishing event handles that, and for examine with unpublished content indexes, we want to keep that data
|
||||
// in the index, it's not until it's complete deleted that we want to remove it.
|
||||
|
||||
//TODO: The Move method of the content service fires Saved/Published events during its execution so we don't need to listen to moved
|
||||
//ContentService.Moved += ContentServiceMoved;
|
||||
ContentService.Trashed += ContentServiceTrashed;
|
||||
|
||||
//public access events
|
||||
Access.AfterSave += Access_AfterSave;
|
||||
}
|
||||
@@ -155,8 +155,31 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
#region Content service event handlers
|
||||
|
||||
static void ContentServiceEmptiedRecycleBin(IContentService sender, RecycleBinEventArgs e)
|
||||
{
|
||||
if (e.RecycleBinEmptiedSuccessfully && e.IsContentRecycleBin)
|
||||
{
|
||||
DistributedCache.Instance.RemoveUnpublishedCachePermanently(e.Ids.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles cache refreshgi for when content is copied
|
||||
/// Handles cache refreshing for when content is trashed
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks>
|
||||
/// This is for the unpublished page refresher - the entity will be unpublished before being moved to the trash
|
||||
/// and the unpublished event will take care of remove it from any published caches
|
||||
/// </remarks>
|
||||
static void ContentServiceTrashed(IContentService sender, MoveEventArgs<IContent> e)
|
||||
{
|
||||
DistributedCache.Instance.RefreshUnpublishedPageCache(
|
||||
e.MoveInfoCollection.Select(x => x.Entity).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles cache refreshing for when content is copied
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
@@ -173,7 +196,7 @@ namespace Umbraco.Web.Cache
|
||||
DistributedCache.Instance.RefreshAllUserPermissionsCache();
|
||||
}
|
||||
|
||||
//run the un-published cache refresher
|
||||
//run the un-published cache refresher since copied content is not published
|
||||
DistributedCache.Instance.RefreshUnpublishedPageCache(e.Copy);
|
||||
}
|
||||
|
||||
@@ -606,19 +629,28 @@ namespace Umbraco.Web.Cache
|
||||
#endregion
|
||||
|
||||
#region Media event handlers
|
||||
static void MediaServiceTrashing(IMediaService sender, MoveEventArgs<IMedia> e)
|
||||
|
||||
static void MediaServiceEmptiedRecycleBin(IMediaService sender, RecycleBinEventArgs e)
|
||||
{
|
||||
DistributedCache.Instance.RemoveMediaCache(false, e.Entity);
|
||||
if (e.RecycleBinEmptiedSuccessfully && e.IsMediaRecycleBin)
|
||||
{
|
||||
DistributedCache.Instance.RemoveMediaCachePermanently(e.Ids.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
static void MediaServiceMoving(IMediaService sender, MoveEventArgs<IMedia> e)
|
||||
static void MediaServiceTrashed(IMediaService sender, MoveEventArgs<IMedia> e)
|
||||
{
|
||||
DistributedCache.Instance.RefreshMediaCache(e.Entity);
|
||||
DistributedCache.Instance.RemoveMediaCacheAfterRecycling(e.MoveInfoCollection.ToArray());
|
||||
}
|
||||
|
||||
static void MediaServiceDeleting(IMediaService sender, DeleteEventArgs<IMedia> e)
|
||||
static void MediaServiceMoved(IMediaService sender, MoveEventArgs<IMedia> e)
|
||||
{
|
||||
DistributedCache.Instance.RemoveMediaCache(true, e.DeletedEntities.ToArray());
|
||||
DistributedCache.Instance.RefreshMediaCacheAfterMoving(e.MoveInfoCollection.ToArray());
|
||||
}
|
||||
|
||||
static void MediaServiceDeleted(IMediaService sender, DeleteEventArgs<IMedia> e)
|
||||
{
|
||||
DistributedCache.Instance.RemoveMediaCachePermanently(e.DeletedEntities.Select(x => x.Id).ToArray());
|
||||
}
|
||||
|
||||
static void MediaServiceSaved(IMediaService sender, SaveEventArgs<IMedia> e)
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Models;
|
||||
using umbraco;
|
||||
using umbraco.cms.businesslogic.web;
|
||||
@@ -264,6 +265,17 @@ namespace Umbraco.Web.Cache
|
||||
dc.Remove(new Guid(DistributedCache.UnpublishedPageCacheRefresherId), x => x.Id, content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// invokes the unpublished page cache refresher to mark all ids for permanent removal
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="contentIds"></param>
|
||||
public static void RemoveUnpublishedCachePermanently(this DistributedCache dc, params int[] contentIds)
|
||||
{
|
||||
dc.RefreshByJson(new Guid(DistributedCache.UnpublishedPageCacheRefresherId),
|
||||
UnpublishedPageCacheRefresher.SerializeToJsonPayloadForPermanentDeletion(contentIds));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Member cache
|
||||
@@ -338,7 +350,7 @@ namespace Umbraco.Web.Cache
|
||||
#region Media Cache
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the cache amongst servers for a media item
|
||||
/// Refreshes the cache amongst servers for media items
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="media"></param>
|
||||
@@ -348,6 +360,18 @@ namespace Umbraco.Web.Cache
|
||||
MediaCacheRefresher.SerializeToJsonPayload(MediaCacheRefresher.OperationType.Saved, media));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the cache amongst servers for a media item after it's been moved
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="media"></param>
|
||||
public static void RefreshMediaCacheAfterMoving(this DistributedCache dc, params MoveEventInfo<IMedia>[] media)
|
||||
{
|
||||
dc.RefreshByJson(new Guid(DistributedCache.MediaCacheRefresherId),
|
||||
MediaCacheRefresher.SerializeToJsonPayloadForMoving(
|
||||
MediaCacheRefresher.OperationType.Saved, media));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the cache amongst servers for a media item
|
||||
/// </summary>
|
||||
@@ -365,18 +389,27 @@ namespace Umbraco.Web.Cache
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the cache amongst servers for media items
|
||||
/// Removes the cache among servers for media items when they are recycled
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="isPermanentlyDeleted"></param>
|
||||
/// <param name="media"></param>
|
||||
public static void RemoveMediaCache(this DistributedCache dc, bool isPermanentlyDeleted, params IMedia[] media)
|
||||
public static void RemoveMediaCacheAfterRecycling(this DistributedCache dc, params MoveEventInfo<IMedia>[] media)
|
||||
{
|
||||
dc.RefreshByJson(new Guid(DistributedCache.MediaCacheRefresherId),
|
||||
MediaCacheRefresher.SerializeToJsonPayload(
|
||||
isPermanentlyDeleted ? MediaCacheRefresher.OperationType.Deleted : MediaCacheRefresher.OperationType.Trashed,
|
||||
media));
|
||||
}
|
||||
MediaCacheRefresher.SerializeToJsonPayloadForMoving(
|
||||
MediaCacheRefresher.OperationType.Trashed, media));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the cache among servers for media items when they are permanently deleted
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <param name="mediaIds"></param>
|
||||
public static void RemoveMediaCachePermanently(this DistributedCache dc, params int[] mediaIds)
|
||||
{
|
||||
dc.RefreshByJson(new Guid(DistributedCache.MediaCacheRefresherId),
|
||||
MediaCacheRefresher.SerializeToJsonPayloadForPermanentDeletion(mediaIds));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Globalization;
|
||||
using System.Web.Script.Serialization;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
using umbraco.interfaces;
|
||||
@@ -47,6 +48,31 @@ namespace Umbraco.Web.Cache
|
||||
return json;
|
||||
}
|
||||
|
||||
internal static string SerializeToJsonPayloadForMoving(OperationType operation, MoveEventInfo<IMedia>[] media)
|
||||
{
|
||||
var serializer = new JavaScriptSerializer();
|
||||
var items = media.Select(x => new JsonPayload
|
||||
{
|
||||
Id = x.Entity.Id,
|
||||
Operation = operation,
|
||||
Path = x.OriginalPath
|
||||
}).ToArray();
|
||||
var json = serializer.Serialize(items);
|
||||
return json;
|
||||
}
|
||||
|
||||
internal static string SerializeToJsonPayloadForPermanentDeletion(params int[] mediaIds)
|
||||
{
|
||||
var serializer = new JavaScriptSerializer();
|
||||
var items = mediaIds.Select(x => new JsonPayload
|
||||
{
|
||||
Id = x,
|
||||
Operation = OperationType.Deleted
|
||||
}).ToArray();
|
||||
var json = serializer.Serialize(items);
|
||||
return json;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a macro to a jsonPayload object
|
||||
/// </summary>
|
||||
@@ -129,17 +155,26 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
payloads.ForEach(payload =>
|
||||
{
|
||||
foreach (var idPart in payload.Path.Split(','))
|
||||
//if there's no path, then just use id (this will occur on permanent deletion like emptying recycle bin)
|
||||
if (payload.Path.IsNullOrWhiteSpace())
|
||||
{
|
||||
ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(
|
||||
string.Format("{0}_{1}_True", CacheKeys.MediaCacheKey, idPart));
|
||||
|
||||
// Also clear calls that only query this specific item!
|
||||
if (idPart == payload.Id.ToString(CultureInfo.InvariantCulture))
|
||||
ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(
|
||||
string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id));
|
||||
|
||||
string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var idPart in payload.Path.Split(','))
|
||||
{
|
||||
ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(
|
||||
string.Format("{0}_{1}_True", CacheKeys.MediaCacheKey, idPart));
|
||||
|
||||
// Also clear calls that only query this specific item!
|
||||
if (idPart == payload.Id.ToString(CultureInfo.InvariantCulture))
|
||||
ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(
|
||||
string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id));
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
using System;
|
||||
using System.Web.Script.Serialization;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Models;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Sync;
|
||||
|
||||
namespace Umbraco.Web.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// A cache refresher used for non-published content, this is primarily to notify Examine indexes to update
|
||||
/// </summary>
|
||||
public sealed class UnpublishedPageCacheRefresher : TypedCacheRefresherBase<UnpublishedPageCacheRefresher, IContent>
|
||||
public sealed class UnpublishedPageCacheRefresher : TypedCacheRefresherBase<UnpublishedPageCacheRefresher, IContent>, IJsonCacheRefresher
|
||||
{
|
||||
|
||||
//NOTE: There is no functionality for this cache refresher, it is here simply to emit events on each server for which examine
|
||||
// binds to. We could put the Examine index functionality in here but we've kept it all in the ExamineEvents class so that all of
|
||||
// the logic is in one place. In the future we may put the examine logic in a cache refresher instead (that would make sense) but we'd
|
||||
// want to get this done before making more cache refreshers:
|
||||
// http://issues.umbraco.org/issue/U4-2633
|
||||
|
||||
protected override UnpublishedPageCacheRefresher Instance
|
||||
{
|
||||
get { return this; }
|
||||
@@ -24,10 +34,58 @@ namespace Umbraco.Web.Cache
|
||||
get { return "Unpublished Page Refresher"; }
|
||||
}
|
||||
|
||||
//NOTE: There is no functionality for this cache refresher, it is here simply to emit events on each server for which examine
|
||||
// binds to. We could put the Examine index functionality in here but we've kept it all in the ExamineEvents class so that all of
|
||||
// the logic is in one place. In the future we may put the examine logic in a cache refresher instead (that would make sense) but we'd
|
||||
// want to get this done before making more cache refreshers:
|
||||
// http://issues.umbraco.org/issue/U4-2633
|
||||
#region Static helpers
|
||||
|
||||
/// <summary>
|
||||
/// Converts the json to a JsonPayload object
|
||||
/// </summary>
|
||||
/// <param name="json"></param>
|
||||
/// <returns></returns>
|
||||
internal static JsonPayload[] DeserializeFromJsonPayload(string json)
|
||||
{
|
||||
var serializer = new JavaScriptSerializer();
|
||||
var jsonObject = serializer.Deserialize<JsonPayload[]>(json);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
|
||||
internal static string SerializeToJsonPayloadForPermanentDeletion(params int[] contentIds)
|
||||
{
|
||||
var serializer = new JavaScriptSerializer();
|
||||
var items = contentIds.Select(x => new JsonPayload
|
||||
{
|
||||
Id = x,
|
||||
Operation = OperationType.Deleted
|
||||
}).ToArray();
|
||||
var json = serializer.Serialize(items);
|
||||
return json;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sub classes
|
||||
|
||||
internal enum OperationType
|
||||
{
|
||||
Deleted
|
||||
}
|
||||
|
||||
internal class JsonPayload
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public OperationType Operation { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Implement the IJsonCacheRefresher so that we can bulk delete the cache based on multiple IDs for when the recycle bin is emptied
|
||||
/// </summary>
|
||||
/// <param name="jsonPayload"></param>
|
||||
public void Refresh(string jsonPayload)
|
||||
{
|
||||
OnCacheUpdated(Instance, new CacheRefresherEventArgs(jsonPayload, MessageType.RefreshByJson));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -158,19 +158,33 @@ namespace Umbraco.Web.Search
|
||||
switch (payload.Operation)
|
||||
{
|
||||
case MediaCacheRefresher.OperationType.Saved:
|
||||
var media = ApplicationContext.Current.Services.MediaService.GetById(payload.Id);
|
||||
if (media != null)
|
||||
var media1 = ApplicationContext.Current.Services.MediaService.GetById(payload.Id);
|
||||
if (media1 != null)
|
||||
{
|
||||
ReIndexForMedia(media, media.Trashed == false);
|
||||
ReIndexForMedia(media1, media1.Trashed == false);
|
||||
}
|
||||
break;
|
||||
case MediaCacheRefresher.OperationType.Trashed:
|
||||
|
||||
//keep if trashed for indexes supporting unpublished
|
||||
//(delete the index from all indexes not supporting unpublished content)
|
||||
|
||||
DeleteIndexForEntity(payload.Id, true);
|
||||
|
||||
//We then need to re-index this item for all indexes supporting unpublished content
|
||||
var media2 = ApplicationContext.Current.Services.MediaService.GetById(payload.Id);
|
||||
if (media2 != null)
|
||||
{
|
||||
ReIndexForMedia(media2, false);
|
||||
}
|
||||
|
||||
break;
|
||||
case MediaCacheRefresher.OperationType.Deleted:
|
||||
|
||||
//permanently remove from all indexes
|
||||
|
||||
DeleteIndexForEntity(payload.Id, false);
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
Reference in New Issue
Block a user