diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs
index cb89ace510..a51da5652e 100644
--- a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs
+++ b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs
@@ -29,7 +29,7 @@ namespace Umbraco.Core.Events
///
/// This is protected so that inheritors can expose it with their own name
///
- protected T EventObject { get; private set; }
+ protected T EventObject { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Events/MoveEventArgs.cs b/src/Umbraco.Core/Events/MoveEventArgs.cs
index 59e7e19f46..107629ff19 100644
--- a/src/Umbraco.Core/Events/MoveEventArgs.cs
+++ b/src/Umbraco.Core/Events/MoveEventArgs.cs
@@ -1,28 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
namespace Umbraco.Core.Events
{
- public class MoveEventArgs : CancellableObjectEventArgs
- {
- public MoveEventArgs(TEntity eventObject, bool canCancel, int parentId) : base(eventObject, canCancel)
- {
- ParentId = parentId;
- }
+ public class MoveEventInfo
+ {
+ public MoveEventInfo(TEntity entity, string originalPath, int newParentId)
+ {
+ Entity = entity;
+ OriginalPath = originalPath;
+ NewParentId = newParentId;
+ }
- public MoveEventArgs(TEntity eventObject, int parentId) : base(eventObject)
- {
- ParentId = parentId;
- }
+ public TEntity Entity { get; set; }
+ public string OriginalPath { get; set; }
+ public int NewParentId { get; set; }
+ }
- ///
- /// The entity being moved
- ///
- public TEntity Entity
- {
- get { return EventObject; }
- }
+ public class MoveEventArgs : CancellableObjectEventArgs
+ {
+ ///
+ /// Constructor accepting a collection of MoveEventInfo objects
+ ///
+ ///
+ ///
+ /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation
+ ///
+ public MoveEventArgs(bool canCancel, params MoveEventInfo[] moveInfo)
+ : base(default(TEntity), canCancel)
+ {
+ if (moveInfo.FirstOrDefault() == null)
+ {
+ throw new ArgumentException("moveInfo argument must contain at least one item");
+ }
- ///
- /// Gets or Sets the Id of the objects new parent.
- ///
- public int ParentId { get; private set; }
- }
+ MoveInfoCollection = moveInfo;
+ //assign the legacy props
+ EventObject = moveInfo.First().Entity;
+ ParentId = moveInfo.First().NewParentId;
+ }
+
+ ///
+ /// Constructor accepting a collection of MoveEventInfo objects
+ ///
+ ///
+ /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation
+ ///
+ public MoveEventArgs(params MoveEventInfo[] moveInfo)
+ : base(default(TEntity))
+ {
+ if (moveInfo.FirstOrDefault() == null)
+ {
+ throw new ArgumentException("moveInfo argument must contain at least one item");
+ }
+
+ MoveInfoCollection = moveInfo;
+ //assign the legacy props
+ EventObject = moveInfo.First().Entity;
+ ParentId = moveInfo.First().NewParentId;
+ }
+
+ [Obsolete("Use the overload that specifies the MoveEventInfo object")]
+ public MoveEventArgs(TEntity eventObject, bool canCancel, int parentId)
+ : base(eventObject, canCancel)
+ {
+ ParentId = parentId;
+ }
+
+ [Obsolete("Use the overload that specifies the MoveEventInfo object")]
+ public MoveEventArgs(TEntity eventObject, int parentId)
+ : base(eventObject)
+ {
+ ParentId = parentId;
+ }
+
+ ///
+ /// Gets all MoveEventInfo objects used to create the object
+ ///
+ public IEnumerable> MoveInfoCollection { get; private set; }
+
+ ///
+ /// The entity being moved
+ ///
+ [Obsolete("Retrieve the entity object from the MoveInfoCollection property instead")]
+ public TEntity Entity
+ {
+ get { return EventObject; }
+ }
+
+ ///
+ /// Gets the Id of the object's new parent
+ ///
+ [Obsolete("Retrieve the ParentId from the MoveInfoCollection property instead")]
+ public int ParentId { get; private set; }
+ }
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index e9d347c970..f6f38d6002 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -968,8 +968,19 @@ namespace Umbraco.Core.Services
{
using (new WriteLock(Locker))
{
- if (Trashing.IsRaisedEventCancelled(new MoveEventArgs(content, -20), this))
+ var originalPath = content.Path;
+
+ if (Trashing.IsRaisedEventCancelled(
+ new MoveEventArgs(
+ new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent)), this))
+ {
return;
+ }
+
+ var moveInfo = new List>
+ {
+ new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent)
+ };
//Make sure that published content is unpublished before being moved to the Recycle Bin
if (HasPublishedVersion(content.Id))
@@ -994,6 +1005,8 @@ namespace Umbraco.Core.Services
//Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId
foreach (var descendant in descendants)
{
+ moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId));
+
descendant.WriterId = userId;
descendant.ChangeTrashedState(true, descendant.ParentId);
repository.AddOrUpdate(descendant);
@@ -1002,7 +1015,7 @@ namespace Umbraco.Core.Services
uow.Commit();
}
- Trashed.RaiseEvent(new MoveEventArgs(content, false, -20), this);
+ Trashed.RaiseEvent(new MoveEventArgs(false, moveInfo.ToArray()), this);
Audit.Add(AuditTypes.Move, "Move Content to Recycle Bin performed by user", userId, content.Id);
}
@@ -1029,76 +1042,21 @@ namespace Umbraco.Core.Services
MoveToRecycleBin(content, userId);
return;
}
-
- if (Moving.IsRaisedEventCancelled(new MoveEventArgs(content, parentId), this))
+
+ if (Moving.IsRaisedEventCancelled(
+ new MoveEventArgs(
+ new MoveEventInfo(content, content.Path, parentId)), this))
+ {
return;
-
- content.WriterId = userId;
- if (parentId == -1)
- {
- content.Path = string.Concat("-1,", content.Id);
- content.Level = 1;
- }
- else
- {
- var parent = GetById(parentId);
- content.Path = string.Concat(parent.Path, ",", content.Id);
- content.Level = parent.Level + 1;
}
+ //used to track all the moved entities to be given to the event
+ var moveInfo = new List>();
- //If Content is being moved away from Recycle Bin, its state should be un-trashed
- if (content.Trashed && parentId != -20)
- {
- content.ChangeTrashedState(false, parentId);
- }
- else
- {
- content.ParentId = parentId;
- }
+ //call private method that does the recursive moving
+ PerformMove(content, parentId, userId, moveInfo);
- //If Content is published, it should be (re)published from its new location
- if (content.Published)
- {
- //If Content is Publishable its saved and published
- //otherwise we save the content without changing the publish state, and generate new xml because the Path, Level and Parent has changed.
- if (IsPublishable(content))
- {
- SaveAndPublish(content, userId);
- }
- else
- {
- Save(content, false, userId, true);
-
- using (var uow = _uowProvider.GetUnitOfWork())
- {
- var xml = content.ToXml();
- var poco = new ContentXmlDto { NodeId = content.Id, Xml = xml.ToString(SaveOptions.None) };
- var exists =
- uow.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = content.Id }) !=
- null;
- int result = exists
- ? uow.Database.Update(poco)
- : Convert.ToInt32(uow.Database.Insert(poco));
- }
- }
- }
- else
- {
- Save(content, userId);
- }
-
- //Ensure that Path and Level is updated on children
- var children = GetChildren(content.Id).ToArray();
- if (children.Any())
- {
- foreach (var child in children)
- {
- Move(child, content.Id, userId);
- }
- }
-
- Moved.RaiseEvent(new MoveEventArgs(content, false, parentId), this);
+ Moved.RaiseEvent(new MoveEventArgs(false, moveInfo.ToArray()), this);
Audit.Add(AuditTypes.Move, "Move Content performed by user", userId, content.Id);
}
@@ -1145,6 +1103,9 @@ namespace Umbraco.Core.Services
/// The newly created object
public IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0)
{
+ //TODO: This all needs to be managed correctly so that the logic is submitted in one
+ // transaction, the CRUD needs to be moved to the repo
+
using (new WriteLock(Locker))
{
var copy = content.DeepCloneWithResetIdentities();
@@ -1167,6 +1128,7 @@ namespace Umbraco.Core.Services
uow.Commit();
//Special case for the Upload DataType
+ //TODO: This really shouldn't be here! What about the cropper in v7, we'll have the same issue.
var uploadDataTypeId = new Guid(Constants.PropertyEditors.UploadField);
if (content.Properties.Any(x => x.PropertyType.DataTypeId == uploadDataTypeId))
{
@@ -1461,6 +1423,79 @@ namespace Umbraco.Core.Services
#region Private Methods
+ private void PerformMove(IContent content, int parentId, int userId, ICollection> moveInfo)
+ {
+ //add a tracking item to use in the Moved event
+ moveInfo.Add(new MoveEventInfo(content, content.Path, parentId));
+
+ content.WriterId = userId;
+ if (parentId == -1)
+ {
+ content.Path = string.Concat("-1,", content.Id);
+ content.Level = 1;
+ }
+ else
+ {
+ var parent = GetById(parentId);
+ content.Path = string.Concat(parent.Path, ",", content.Id);
+ content.Level = parent.Level + 1;
+ }
+
+ //If Content is being moved away from Recycle Bin, its state should be un-trashed
+ if (content.Trashed && parentId != Constants.System.RecycleBinContent)
+ {
+ content.ChangeTrashedState(false, parentId);
+ }
+ else
+ {
+ content.ParentId = parentId;
+ }
+
+ //If Content is published, it should be (re)published from its new location
+ if (content.Published)
+ {
+ //If Content is Publishable its saved and published
+ //otherwise we save the content without changing the publish state, and generate new xml because the Path, Level and Parent has changed.
+ if (IsPublishable(content))
+ {
+ //TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine
+ SaveAndPublish(content, userId);
+ }
+ else
+ {
+ //TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine
+ Save(content, false, userId);
+
+ using (var uow = _uowProvider.GetUnitOfWork())
+ {
+ var xml = content.ToXml();
+ var poco = new ContentXmlDto { NodeId = content.Id, Xml = xml.ToString(SaveOptions.None) };
+ var exists =
+ uow.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = content.Id }) !=
+ null;
+ int result = exists
+ ? uow.Database.Update(poco)
+ : Convert.ToInt32(uow.Database.Insert(poco));
+ }
+ }
+ }
+ else
+ {
+ //TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine
+ Save(content, userId);
+ }
+
+ //Ensure that Path and Level is updated on children
+ var children = GetChildren(content.Id).ToArray();
+ if (children.Any())
+ {
+ foreach (var child in children)
+ {
+ PerformMove(child, content.Id, userId, moveInfo);
+ }
+ }
+ }
+
//TODO: WE should make a base class for ContentService and MediaService to share!
// currently we have this logic duplicated (nearly the same) for media types and soon to be member types
diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs
index dfeb271936..865a8416cc 100644
--- a/src/Umbraco.Core/Services/MediaService.cs
+++ b/src/Umbraco.Core/Services/MediaService.cs
@@ -516,15 +516,29 @@ namespace Umbraco.Core.Services
return;
}
- if (Moving.IsRaisedEventCancelled(new MoveEventArgs(media, parentId), this))
- return;
+ var originalPath = media.Path;
+ if (Moving.IsRaisedEventCancelled(
+ new MoveEventArgs(
+ new MoveEventInfo(media, originalPath, parentId)), this))
+ {
+ return;
+ }
+
media.ParentId = parentId;
if (media.Trashed)
{
media.ChangeTrashedState(false, parentId);
}
- Save(media, userId);
+ Save(media, userId,
+ //no events!
+ false);
+
+ //used to track all the moved entities to be given to the event
+ var moveInfo = new List>
+ {
+ new MoveEventInfo(media, originalPath, parentId)
+ };
//Ensure that relevant properties are updated on children
var children = GetChildren(media.Id).ToArray();
@@ -533,11 +547,13 @@ namespace Umbraco.Core.Services
var parentPath = media.Path;
var parentLevel = media.Level;
var parentTrashed = media.Trashed;
- var updatedDescendants = UpdatePropertiesOnChildren(children, parentPath, parentLevel, parentTrashed);
- Save(updatedDescendants, userId);
+ var updatedDescendants = UpdatePropertiesOnChildren(children, parentPath, parentLevel, parentTrashed, moveInfo);
+ Save(updatedDescendants, userId,
+ //no events!
+ false);
}
- Moved.RaiseEvent(new MoveEventArgs(media, false, parentId), this);
+ Moved.RaiseEvent(new MoveEventArgs(false, moveInfo.ToArray()), this);
Audit.Add(AuditTypes.Move, "Move Media performed by user", userId, media.Id);
}
@@ -552,8 +568,19 @@ namespace Umbraco.Core.Services
{
if (media == null) throw new ArgumentNullException("media");
- if (Trashing.IsRaisedEventCancelled(new MoveEventArgs(media, -21), this))
+ var originalPath = media.Path;
+
+ if (Trashing.IsRaisedEventCancelled(
+ new MoveEventArgs(
+ new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia)), this))
+ {
return;
+ }
+
+ var moveInfo = new List>
+ {
+ new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia)
+ };
//Find Descendants, which will be moved to the recycle bin along with the parent/grandparent.
var descendants = GetDescendants(media).OrderBy(x => x.Level).ToList();
@@ -561,12 +588,14 @@ namespace Umbraco.Core.Services
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateMediaRepository(uow))
{
+ //TODO: This should be part of the repo!
+
//Remove 'published' xml from the cmsContentXml table for the unpublished media
uow.Database.Delete("WHERE nodeId = @Id", new { Id = media.Id });
- media.ChangeTrashedState(true, -21);
+ media.ChangeTrashedState(true, Constants.System.RecycleBinMedia);
repository.AddOrUpdate(media);
-
+
//Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId
foreach (var descendant in descendants)
{
@@ -575,12 +604,14 @@ namespace Umbraco.Core.Services
descendant.ChangeTrashedState(true, descendant.ParentId);
repository.AddOrUpdate(descendant);
+
+ moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId));
}
uow.Commit();
}
- Trashed.RaiseEvent(new MoveEventArgs(media, false, -21), this);
+ Trashed.RaiseEvent(new MoveEventArgs(false, moveInfo.ToArray()), this);
Audit.Add(AuditTypes.Move, "Move Media to Recycle Bin performed by user", userId, media.Id);
}
@@ -978,12 +1009,14 @@ namespace Umbraco.Core.Services
/// Path of the Parent media
/// Level of the Parent media
/// Indicates whether the Parent is trashed or not
+ /// Used to track the objects to be used in the move event
/// Collection of updated objects
- private IEnumerable UpdatePropertiesOnChildren(IEnumerable children, string parentPath, int parentLevel, bool parentTrashed)
+ private IEnumerable UpdatePropertiesOnChildren(IEnumerable children, string parentPath, int parentLevel, bool parentTrashed, ICollection> eventInfo)
{
var list = new List();
foreach (var child in children)
{
+ var originalPath = child.Path;
child.Path = string.Concat(parentPath, ",", child.Id);
child.Level = parentLevel + 1;
if (parentTrashed != child.Trashed)
@@ -991,12 +1024,13 @@ namespace Umbraco.Core.Services
child.ChangeTrashedState(parentTrashed, child.ParentId);
}
+ eventInfo.Add(new MoveEventInfo(child, originalPath, child.ParentId));
list.Add(child);
var grandkids = GetChildren(child.Id).ToArray();
if (grandkids.Any())
{
- list.AddRange(UpdatePropertiesOnChildren(grandkids, child.Path, child.Level, child.Trashed));
+ list.AddRange(UpdatePropertiesOnChildren(grandkids, child.Path, child.Level, child.Trashed, eventInfo));
}
}
return list;
diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs
index 469463d7a2..3d8da381d1 100644
--- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs
+++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs
@@ -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());
+ }
+ }
+
///
- /// Handles cache refreshgi for when content is copied
+ /// Handles cache refreshing for when content is trashed
+ ///
+ ///
+ ///
+ ///
+ /// 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
+ ///
+ static void ContentServiceTrashed(IContentService sender, MoveEventArgs e)
+ {
+ DistributedCache.Instance.RefreshUnpublishedPageCache(
+ e.MoveInfoCollection.Select(x => x.Entity).ToArray());
+ }
+
+ ///
+ /// Handles cache refreshing for when content is copied
///
///
///
@@ -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 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 e)
+ static void MediaServiceTrashed(IMediaService sender, MoveEventArgs e)
{
- DistributedCache.Instance.RefreshMediaCache(e.Entity);
+ DistributedCache.Instance.RemoveMediaCacheAfterRecycling(e.MoveInfoCollection.ToArray());
}
- static void MediaServiceDeleting(IMediaService sender, DeleteEventArgs e)
+ static void MediaServiceMoved(IMediaService sender, MoveEventArgs e)
{
- DistributedCache.Instance.RemoveMediaCache(true, e.DeletedEntities.ToArray());
+ DistributedCache.Instance.RefreshMediaCacheAfterMoving(e.MoveInfoCollection.ToArray());
+ }
+
+ static void MediaServiceDeleted(IMediaService sender, DeleteEventArgs e)
+ {
+ DistributedCache.Instance.RemoveMediaCachePermanently(e.DeletedEntities.Select(x => x.Id).ToArray());
}
static void MediaServiceSaved(IMediaService sender, SaveEventArgs e)
diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs
index 7263776ae5..73234ed1a8 100644
--- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs
+++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs
@@ -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);
}
+ ///
+ /// invokes the unpublished page cache refresher to mark all ids for permanent removal
+ ///
+ ///
+ ///
+ 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
///
- /// Refreshes the cache amongst servers for a media item
+ /// Refreshes the cache amongst servers for media items
///
///
///
@@ -348,6 +360,18 @@ namespace Umbraco.Web.Cache
MediaCacheRefresher.SerializeToJsonPayload(MediaCacheRefresher.OperationType.Saved, media));
}
+ ///
+ /// Refreshes the cache amongst servers for a media item after it's been moved
+ ///
+ ///
+ ///
+ public static void RefreshMediaCacheAfterMoving(this DistributedCache dc, params MoveEventInfo[] media)
+ {
+ dc.RefreshByJson(new Guid(DistributedCache.MediaCacheRefresherId),
+ MediaCacheRefresher.SerializeToJsonPayloadForMoving(
+ MediaCacheRefresher.OperationType.Saved, media));
+ }
+
///
/// Removes the cache amongst servers for a media item
///
@@ -365,18 +389,27 @@ namespace Umbraco.Web.Cache
}
///
- /// Removes the cache amongst servers for media items
+ /// Removes the cache among servers for media items when they are recycled
///
///
- ///
///
- public static void RemoveMediaCache(this DistributedCache dc, bool isPermanentlyDeleted, params IMedia[] media)
+ public static void RemoveMediaCacheAfterRecycling(this DistributedCache dc, params MoveEventInfo[] media)
{
dc.RefreshByJson(new Guid(DistributedCache.MediaCacheRefresherId),
- MediaCacheRefresher.SerializeToJsonPayload(
- isPermanentlyDeleted ? MediaCacheRefresher.OperationType.Deleted : MediaCacheRefresher.OperationType.Trashed,
- media));
- }
+ MediaCacheRefresher.SerializeToJsonPayloadForMoving(
+ MediaCacheRefresher.OperationType.Trashed, media));
+ }
+
+ ///
+ /// Removes the cache among servers for media items when they are permanently deleted
+ ///
+ ///
+ ///
+ public static void RemoveMediaCachePermanently(this DistributedCache dc, params int[] mediaIds)
+ {
+ dc.RefreshByJson(new Guid(DistributedCache.MediaCacheRefresherId),
+ MediaCacheRefresher.SerializeToJsonPayloadForPermanentDeletion(mediaIds));
+ }
#endregion
diff --git a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs
index a2de17626f..9074e4856c 100644
--- a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs
+++ b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs
@@ -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[] 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;
+ }
+
///
/// Converts a macro to a jsonPayload object
///
@@ -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));
+
+ }
+ }
});
diff --git a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs
index 4413a61e9b..765c591415 100644
--- a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs
+++ b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs
@@ -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
{
///
/// A cache refresher used for non-published content, this is primarily to notify Examine indexes to update
///
- public sealed class UnpublishedPageCacheRefresher : TypedCacheRefresherBase
+ public sealed class UnpublishedPageCacheRefresher : TypedCacheRefresherBase, 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
+
+ ///
+ /// Converts the json to a JsonPayload object
+ ///
+ ///
+ ///
+ internal static JsonPayload[] DeserializeFromJsonPayload(string json)
+ {
+ var serializer = new JavaScriptSerializer();
+ var jsonObject = serializer.Deserialize(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
+
+ ///
+ /// Implement the IJsonCacheRefresher so that we can bulk delete the cache based on multiple IDs for when the recycle bin is emptied
+ ///
+ ///
+ public void Refresh(string jsonPayload)
+ {
+ OnCacheUpdated(Instance, new CacheRefresherEventArgs(jsonPayload, MessageType.RefreshByJson));
+ }
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineEvents.cs
index c04920e289..4507a1a92a 100644
--- a/src/Umbraco.Web/Search/ExamineEvents.cs
+++ b/src/Umbraco.Web/Search/ExamineEvents.cs
@@ -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();