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:
Shannon
2014-04-23 20:19:36 +10:00
parent 949447c25f
commit 021cac9ca5
9 changed files with 458 additions and 147 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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));
}
}
});

View File

@@ -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));
}
}
}

View File

@@ -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();