From 0e06245177205bd75742f0f246c236222a747e8b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 6 Feb 2017 14:46:02 +1100 Subject: [PATCH 1/3] Adds notes for stephen to look at --- src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs | 5 ++++- src/Umbraco.Web/umbraco.presentation/content.cs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs b/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs index 581f0fec97..9ec95ae45b 100644 --- a/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs +++ b/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs @@ -69,7 +69,10 @@ namespace umbraco if (_isWriter) throw new InvalidOperationException("Already a writer."); _isWriter = true; - _xml = Clone(_xml); // cloning for writer is not an option anymore (see XmlIsImmutable) + + // cloning for writer is not an option anymore (see XmlIsImmutable) + //fixme: But XmlIsImmutable is not actually used! + _xml = Clone(_xml); } internal static Action Cloning { get; set; } diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 8db4e9122f..fcbf4dfdf3 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -562,7 +562,8 @@ namespace umbraco { get { return XmlFileEnabled && UmbracoConfig.For.UmbracoSettings().Content.XmlContentCheckForDiskChanges; } } - + + //fixme: this is not used? // whether _xml is immutable or not (achieved by cloning before changing anything) private static bool XmlIsImmutable { From f6a7b25e87c5c9a5200c5b8aa99cfcf22d20bb1b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 6 Feb 2017 17:26:06 +1100 Subject: [PATCH 2/3] Updates CacheRefresherEventHandler to have consistent events, adds logic to find the event based on definitions, adds tests to test them all --- .../Events/EventDefinitionBase.cs | 5 +- src/Umbraco.Core/Events/EventNameExtractor.cs | 33 +- .../Cache/CacheRefresherEventHandlerTests.cs | 108 ++++++ .../Cache/SingleItemsOnlyCachePolicyTests.cs | 2 + .../TestHelpers/BaseDatabaseFactoryTest.cs | 7 +- .../TestHelpers/BaseUmbracoApplicationTest.cs | 5 + src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Cache/CacheRefresherEventHandler.cs | 328 +++++++++++------- 8 files changed, 337 insertions(+), 152 deletions(-) create mode 100644 src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs diff --git a/src/Umbraco.Core/Events/EventDefinitionBase.cs b/src/Umbraco.Core/Events/EventDefinitionBase.cs index 46e57306a5..bb107ea09c 100644 --- a/src/Umbraco.Core/Events/EventDefinitionBase.cs +++ b/src/Umbraco.Core/Events/EventDefinitionBase.cs @@ -15,7 +15,10 @@ namespace Umbraco.Core.Events if (EventName.IsNullOrWhiteSpace()) { - var findResult = EventNameExtractor.FindEvent(sender, args, EventNameExtractor.MatchIngNames); + var findResult = EventNameExtractor.FindEvent(sender, args, + //don't match "Ing" suffixed names + exclude:EventNameExtractor.MatchIngNames); + if (findResult.Success == false) throw new AmbiguousMatchException("Could not automatically find the event name, the event name will need to be explicitly registered for this event definition. Error: " + findResult.Result.Error); EventName = findResult.Result.Name; diff --git a/src/Umbraco.Core/Events/EventNameExtractor.cs b/src/Umbraco.Core/Events/EventNameExtractor.cs index 3630763405..33137770d4 100644 --- a/src/Umbraco.Core/Events/EventNameExtractor.cs +++ b/src/Umbraco.Core/Events/EventNameExtractor.cs @@ -22,23 +22,20 @@ namespace Umbraco.Core.Events /// internal class EventNameExtractor { + /// /// Finds the event name on the sender that matches the args type /// - /// - /// + /// + /// /// /// A filter to exclude matched event names, this filter should return true to exclude the event name from being matched /// /// /// null if not found or an ambiguous match /// - public static Attempt FindEvent(object sender, object args, Func exclude) - { - var argsType = args.GetType(); - - var senderType = sender.GetType(); - + public static Attempt FindEvent(Type senderType, Type argsType, Func exclude) + { var found = MatchedEventNames.GetOrAdd(new Tuple(senderType, argsType), tuple => { var events = CandidateEvents.GetOrAdd(senderType, t => @@ -70,14 +67,14 @@ namespace Umbraco.Core.Events return true; //special case for our own TypedEventHandler - if (x.EventInfo.EventHandlerType.GetGenericTypeDefinition() == typeof(TypedEventHandler<,>) + if (x.EventInfo.EventHandlerType.GetGenericTypeDefinition() == typeof(TypedEventHandler<,>) && x.GenericArgs.Length == 2 && x.GenericArgs[1] == tuple.Item2) { return true; } - return false; + return false; }).Select(x => x.EventInfo.Name).ToArray(); }); @@ -93,6 +90,22 @@ namespace Umbraco.Core.Events return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.Ambiguous)); } + /// + /// Finds the event name on the sender that matches the args type + /// + /// + /// + /// + /// A filter to exclude matched event names, this filter should return true to exclude the event name from being matched + /// + /// + /// null if not found or an ambiguous match + /// + public static Attempt FindEvent(object sender, object args, Func exclude) + { + return FindEvent(sender.GetType(), args.GetType(), exclude); + } + /// /// Return true if the event is named with an ING name such as "Saving" or "RollingBack" /// diff --git a/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs b/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs new file mode 100644 index 0000000000..3aa1db675f --- /dev/null +++ b/src/Umbraco.Tests/Cache/CacheRefresherEventHandlerTests.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web.Cache; + +namespace Umbraco.Tests.Cache +{ + [TestFixture] + public class CacheRefresherEventHandlerTests : BaseUmbracoApplicationTest + { + [Test] + public void Can_Find_All_Event_Handlers() + { + var definitions = new IEventDefinition[] + { + //I would test these but they are legacy events and we don't need them for deploy, when we migrate to new/better events we can wire up the check + //Permission.New += PermissionNew; + //Permission.Updated += PermissionUpdated; + //Permission.Deleted += PermissionDeleted; + //PermissionRepository.AssignedPermissions += CacheRefresherEventHandler_AssignedPermissions; + + new EventDefinition(null, ServiceContext.ApplicationTreeService, new EventArgs(), "Deleted"), + new EventDefinition(null, ServiceContext.ApplicationTreeService, new EventArgs(), "Updated"), + new EventDefinition(null, ServiceContext.ApplicationTreeService, new EventArgs(), "New"), + + new EventDefinition(null, ServiceContext.SectionService, new EventArgs(), "Deleted"), + new EventDefinition(null, ServiceContext.SectionService, new EventArgs(), "New"), + + new EventDefinition>(null, ServiceContext.UserService, new SaveEventArgs((IUserType) null)), + new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs((IUserType) null)), + + new EventDefinition>(null, ServiceContext.UserService, new SaveEventArgs((IUser) null)), + new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs((IUser) null)), + new EventDefinition>(null, ServiceContext.UserService, new DeleteEventArgs((IUser) null)), + + new EventDefinition>(null, ServiceContext.LocalizationService, new SaveEventArgs((IDictionaryItem) null)), + new EventDefinition>(null, ServiceContext.LocalizationService, new DeleteEventArgs((IDictionaryItem) null)), + + new EventDefinition>(null, ServiceContext.DataTypeService, new SaveEventArgs((IDataTypeDefinition) null)), + new EventDefinition>(null, ServiceContext.DataTypeService, new DeleteEventArgs((IDataTypeDefinition) null)), + + new EventDefinition>(null, ServiceContext.FileService, new SaveEventArgs((Stylesheet) null)), + new EventDefinition>(null, ServiceContext.FileService, new DeleteEventArgs((Stylesheet) null)), + + new EventDefinition>(null, ServiceContext.DomainService, new SaveEventArgs((IDomain) null)), + new EventDefinition>(null, ServiceContext.DomainService, new DeleteEventArgs((IDomain) null)), + + new EventDefinition>(null, ServiceContext.LocalizationService, new SaveEventArgs((ILanguage) null)), + new EventDefinition>(null, ServiceContext.LocalizationService, new DeleteEventArgs((ILanguage) null)), + + new EventDefinition>(null, ServiceContext.ContentTypeService, new SaveEventArgs((IContentType) null)), + new EventDefinition>(null, ServiceContext.ContentTypeService, new DeleteEventArgs((IContentType) null)), + new EventDefinition>(null, ServiceContext.ContentTypeService, new SaveEventArgs((IMediaType) null)), + new EventDefinition>(null, ServiceContext.ContentTypeService, new DeleteEventArgs((IMediaType) null)), + + new EventDefinition>(null, ServiceContext.MemberTypeService, new SaveEventArgs((IMemberType) null)), + new EventDefinition>(null, ServiceContext.MemberTypeService, new DeleteEventArgs((IMemberType) null)), + + new EventDefinition>(null, ServiceContext.FileService, new SaveEventArgs((ITemplate) null)), + new EventDefinition>(null, ServiceContext.FileService, new DeleteEventArgs((ITemplate) null)), + + new EventDefinition>(null, ServiceContext.MacroService, new SaveEventArgs((IMacro) null)), + new EventDefinition>(null, ServiceContext.MacroService, new DeleteEventArgs((IMacro) null)), + + new EventDefinition>(null, ServiceContext.MemberService, new SaveEventArgs((IMember) null)), + new EventDefinition>(null, ServiceContext.MemberService, new DeleteEventArgs((IMember) null)), + + new EventDefinition>(null, ServiceContext.MemberGroupService, new SaveEventArgs((IMemberGroup) null)), + new EventDefinition>(null, ServiceContext.MemberGroupService, new DeleteEventArgs((IMemberGroup) null)), + + new EventDefinition>(null, ServiceContext.MediaService, new SaveEventArgs((IMedia) null)), + new EventDefinition>(null, ServiceContext.MediaService, new DeleteEventArgs((IMedia) null)), + new EventDefinition>(null, ServiceContext.MediaService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Moved"), + new EventDefinition>(null, ServiceContext.MediaService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Trashed"), + new EventDefinition(null, ServiceContext.MediaService, new RecycleBinEventArgs(Guid.NewGuid(), new Dictionary>(), true)), + + new EventDefinition>(null, ServiceContext.ContentService, new SaveEventArgs((IContent) null)), + new EventDefinition>(null, ServiceContext.ContentService, new DeleteEventArgs((IContent) null)), + new EventDefinition>(null, ServiceContext.ContentService, new CopyEventArgs(null, null, -1)), + new EventDefinition>(null, ServiceContext.ContentService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Trashed"), + new EventDefinition(null, ServiceContext.ContentService, new RecycleBinEventArgs(Guid.NewGuid(), new Dictionary>(), true)), + new EventDefinition>(null, ServiceContext.ContentService, new PublishEventArgs((IContent) null), "Published"), + new EventDefinition>(null, ServiceContext.ContentService, new PublishEventArgs((IContent) null), "UnPublished"), + + new EventDefinition>(null, ServiceContext.PublicAccessService, new SaveEventArgs((PublicAccessEntry) null)), + new EventDefinition>(null, ServiceContext.PublicAccessService, new DeleteEventArgs((PublicAccessEntry) null)), + + new EventDefinition>(null, ServiceContext.RelationService, new SaveEventArgs((IRelationType) null)), + new EventDefinition>(null, ServiceContext.RelationService, new DeleteEventArgs((IRelationType) null)), + + new EventDefinition>(null, ServiceContext.RelationService, new SaveEventArgs((IRelationType) null)), + new EventDefinition>(null, ServiceContext.RelationService, new DeleteEventArgs((IRelationType) null)), + }; + + foreach (var definition in definitions) + { + var found = CacheRefresherEventHandler.FindHandler(definition); + Assert.IsNotNull(found, "Couldn't find method for " + definition.EventName + " on " + definition.Sender.GetType()); + } + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs b/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs index 55a7f2a893..2ec175933e 100644 --- a/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Web.Caching; using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 57595578da..70f48b12fb 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -352,12 +352,7 @@ namespace Umbraco.Tests.TestHelpers onFail(ex); } } - - protected ServiceContext ServiceContext - { - get { return ApplicationContext.Services; } - } - + protected DatabaseContext DatabaseContext { get { return ApplicationContext.DatabaseContext; } diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index 98f7b36e81..43b6171b94 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -208,6 +208,11 @@ namespace Umbraco.Tests.TestHelpers Resolution.Freeze(); } + protected ServiceContext ServiceContext + { + get { return ApplicationContext.Services; } + } + protected ApplicationContext ApplicationContext { get { return ApplicationContext.Current; } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 10810b0a1f..46c8387de8 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -156,6 +156,7 @@ + diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 23e064ed9c..59084f13c4 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using Umbraco.Core; using Umbraco.Core.Events; @@ -9,6 +10,7 @@ using Umbraco.Core.Services; using umbraco.BusinessLogic; using umbraco.cms.businesslogic; using System.Linq; +using System.Reflection; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Publishing; @@ -29,35 +31,35 @@ namespace Umbraco.Web.Cache LogHelper.Info("Initializing Umbraco internal event handlers for cache refreshing"); //bind to application tree events - ApplicationTreeService.Deleted += ApplicationTreeDeleted; - ApplicationTreeService.Updated += ApplicationTreeUpdated; - ApplicationTreeService.New += ApplicationTreeNew; + ApplicationTreeService.Deleted += ApplicationTreeService_Deleted; + ApplicationTreeService.Updated += ApplicationTreeService_Updated; + ApplicationTreeService.New += ApplicationTreeService_New; //bind to application events - SectionService.Deleted += ApplicationDeleted; - SectionService.New += ApplicationNew; + SectionService.Deleted += SectionService_Deleted; + SectionService.New += SectionService_New; //bind to user / user type events - UserService.SavedUserType += UserServiceSavedUserType; - UserService.DeletedUserType += UserServiceDeletedUserType; - UserService.SavedUser += UserServiceSavedUser; - UserService.DeletedUser += UserServiceDeletedUser; + UserService.SavedUserType += UserService_SavedUserType; + UserService.DeletedUserType += UserService_DeletedUserType; + UserService.SavedUser += UserService_SavedUser; + UserService.DeletedUser += UserService_DeletedUser; //Bind to dictionary events - LocalizationService.DeletedDictionaryItem += LocalizationServiceDeletedDictionaryItem; - LocalizationService.SavedDictionaryItem += LocalizationServiceSavedDictionaryItem; + LocalizationService.DeletedDictionaryItem += LocalizationService_DeletedDictionaryItem; + LocalizationService.SavedDictionaryItem += LocalizationService_SavedDictionaryItem; //Bind to data type events //NOTE: we need to bind to legacy and new API events currently: http://issues.umbraco.org/issue/U4-1979 - DataTypeService.Deleted += DataTypeServiceDeleted; - DataTypeService.Saved += DataTypeServiceSaved; + DataTypeService.Deleted += DataTypeService_Deleted; + DataTypeService.Saved += DataTypeService_Saved; //Bind to stylesheet events - FileService.SavedStylesheet += FileServiceSavedStylesheet; - FileService.DeletedStylesheet += FileServiceDeletedStylesheet; + FileService.SavedStylesheet += FileService_SavedStylesheet; + FileService.DeletedStylesheet += FileService_DeletedStylesheet; //Bind to domain events @@ -66,17 +68,17 @@ namespace Umbraco.Web.Cache //Bind to language events - LocalizationService.SavedLanguage += LocalizationServiceSavedLanguage; - LocalizationService.DeletedLanguage += LocalizationServiceDeletedLanguage; + LocalizationService.SavedLanguage += LocalizationService_SavedLanguage; + LocalizationService.DeletedLanguage += LocalizationService_DeletedLanguage; //Bind to content type events - ContentTypeService.SavedContentType += ContentTypeServiceSavedContentType; - ContentTypeService.SavedMediaType += ContentTypeServiceSavedMediaType; - ContentTypeService.DeletedContentType += ContentTypeServiceDeletedContentType; - ContentTypeService.DeletedMediaType += ContentTypeServiceDeletedMediaType; - MemberTypeService.Saved += MemberTypeServiceSaved; - MemberTypeService.Deleted += MemberTypeServiceDeleted; + ContentTypeService.SavedContentType += ContentTypeService_SavedContentType; + ContentTypeService.SavedMediaType += ContentTypeService_SavedMediaType; + ContentTypeService.DeletedContentType += ContentTypeService_DeletedContentType; + ContentTypeService.DeletedMediaType += ContentTypeService_DeletedMediaType; + MemberTypeService.Saved += MemberTypeService_Saved; + MemberTypeService.Deleted += MemberTypeService_Deleted; //Bind to permission events @@ -88,83 +90,83 @@ namespace Umbraco.Web.Cache //Bind to template events - FileService.SavedTemplate += FileServiceSavedTemplate; - FileService.DeletedTemplate += FileServiceDeletedTemplate; + FileService.SavedTemplate += FileService_SavedTemplate; + FileService.DeletedTemplate += FileService_DeletedTemplate; //Bind to macro events - MacroService.Saved += MacroServiceSaved; - MacroService.Deleted += MacroServiceDeleted; + MacroService.Saved += MacroService_Saved; + MacroService.Deleted += MacroService_Deleted; //Bind to member events - MemberService.Saved += MemberServiceSaved; - MemberService.Deleted += MemberServiceDeleted; + MemberService.Saved += MemberService_Saved; + MemberService.Deleted += MemberService_Deleted; MemberGroupService.Saved += MemberGroupService_Saved; MemberGroupService.Deleted += MemberGroupService_Deleted; //Bind to media events - MediaService.Saved += MediaServiceSaved; - MediaService.Deleted += MediaServiceDeleted; - MediaService.Moved += MediaServiceMoved; - MediaService.Trashed += MediaServiceTrashed; - MediaService.EmptiedRecycleBin += MediaServiceEmptiedRecycleBin; + MediaService.Saved += MediaService_Saved; + MediaService.Deleted += MediaService_Deleted; + MediaService.Moved += MediaService_Moved; + MediaService.Trashed += MediaService_Trashed; + MediaService.EmptiedRecycleBin += MediaService_EmptiedRecycleBin; //Bind to content events - this is for unpublished content syncing across servers (primarily for examine) - ContentService.Saved += ContentServiceSaved; - ContentService.Deleted += ContentServiceDeleted; - ContentService.Copied += ContentServiceCopied; + ContentService.Saved += ContentService_Saved; + ContentService.Deleted += ContentService_Deleted; + ContentService.Copied += ContentService_Copied; //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; - ContentService.EmptiedRecycleBin += ContentServiceEmptiedRecycleBin; + ContentService.Trashed += ContentService_Trashed; + ContentService.EmptiedRecycleBin += ContentService_EmptiedRecycleBin; - PublishingStrategy.Published += PublishingStrategy_Published; - PublishingStrategy.UnPublished += PublishingStrategy_UnPublished; + ContentService.Published += ContentService_Published; + ContentService.UnPublished += ContentService_UnPublished; //public access events PublicAccessService.Saved += PublicAccessService_Saved; PublicAccessService.Deleted += PublicAccessService_Deleted; - RelationService.SavedRelationType += RelationType_Saved; - RelationService.DeletedRelationType += RelationType_Deleted; + RelationService.SavedRelationType += RelationService_SavedRelationType; + RelationService.DeletedRelationType += RelationService_DeletedRelationType; } // for tests internal void Destroy() { //bind to application tree events - ApplicationTreeService.Deleted -= ApplicationTreeDeleted; - ApplicationTreeService.Updated -= ApplicationTreeUpdated; - ApplicationTreeService.New -= ApplicationTreeNew; + ApplicationTreeService.Deleted -= ApplicationTreeService_Deleted; + ApplicationTreeService.Updated -= ApplicationTreeService_Updated; + ApplicationTreeService.New -= ApplicationTreeService_New; //bind to application events - SectionService.Deleted -= ApplicationDeleted; - SectionService.New -= ApplicationNew; + SectionService.Deleted -= SectionService_Deleted; + SectionService.New -= SectionService_New; //bind to user / user type events - UserService.SavedUserType -= UserServiceSavedUserType; - UserService.DeletedUserType -= UserServiceDeletedUserType; - UserService.SavedUser -= UserServiceSavedUser; - UserService.DeletedUser -= UserServiceDeletedUser; + UserService.SavedUserType -= UserService_SavedUserType; + UserService.DeletedUserType -= UserService_DeletedUserType; + UserService.SavedUser -= UserService_SavedUser; + UserService.DeletedUser -= UserService_DeletedUser; //Bind to dictionary events - LocalizationService.DeletedDictionaryItem -= LocalizationServiceDeletedDictionaryItem; - LocalizationService.SavedDictionaryItem -= LocalizationServiceSavedDictionaryItem; + LocalizationService.DeletedDictionaryItem -= LocalizationService_DeletedDictionaryItem; + LocalizationService.SavedDictionaryItem -= LocalizationService_SavedDictionaryItem; //Bind to data type events //NOTE: we need to bind to legacy and new API events currently: http://issues.umbraco.org/issue/U4-1979 - DataTypeService.Deleted -= DataTypeServiceDeleted; - DataTypeService.Saved -= DataTypeServiceSaved; + DataTypeService.Deleted -= DataTypeService_Deleted; + DataTypeService.Saved -= DataTypeService_Saved; //Bind to stylesheet events - FileService.SavedStylesheet -= FileServiceSavedStylesheet; - FileService.DeletedStylesheet -= FileServiceDeletedStylesheet; + FileService.SavedStylesheet -= FileService_SavedStylesheet; + FileService.DeletedStylesheet -= FileService_DeletedStylesheet; //Bind to domain events @@ -173,17 +175,17 @@ namespace Umbraco.Web.Cache //Bind to language events - LocalizationService.SavedLanguage -= LocalizationServiceSavedLanguage; - LocalizationService.DeletedLanguage -= LocalizationServiceDeletedLanguage; + LocalizationService.SavedLanguage -= LocalizationService_SavedLanguage; + LocalizationService.DeletedLanguage -= LocalizationService_DeletedLanguage; //Bind to content type events - ContentTypeService.SavedContentType -= ContentTypeServiceSavedContentType; - ContentTypeService.SavedMediaType -= ContentTypeServiceSavedMediaType; - ContentTypeService.DeletedContentType -= ContentTypeServiceDeletedContentType; - ContentTypeService.DeletedMediaType -= ContentTypeServiceDeletedMediaType; - MemberTypeService.Saved -= MemberTypeServiceSaved; - MemberTypeService.Deleted -= MemberTypeServiceDeleted; + ContentTypeService.SavedContentType -= ContentTypeService_SavedContentType; + ContentTypeService.SavedMediaType -= ContentTypeService_SavedMediaType; + ContentTypeService.DeletedContentType -= ContentTypeService_DeletedContentType; + ContentTypeService.DeletedMediaType -= ContentTypeService_DeletedMediaType; + MemberTypeService.Saved -= MemberTypeService_Saved; + MemberTypeService.Deleted -= MemberTypeService_Deleted; //Bind to permission events @@ -195,53 +197,53 @@ namespace Umbraco.Web.Cache //Bind to template events - FileService.SavedTemplate -= FileServiceSavedTemplate; - FileService.DeletedTemplate -= FileServiceDeletedTemplate; + FileService.SavedTemplate -= FileService_SavedTemplate; + FileService.DeletedTemplate -= FileService_DeletedTemplate; //Bind to macro events - MacroService.Saved -= MacroServiceSaved; - MacroService.Deleted -= MacroServiceDeleted; + MacroService.Saved -= MacroService_Saved; + MacroService.Deleted -= MacroService_Deleted; //Bind to member events - MemberService.Saved -= MemberServiceSaved; - MemberService.Deleted -= MemberServiceDeleted; + MemberService.Saved -= MemberService_Saved; + MemberService.Deleted -= MemberService_Deleted; MemberGroupService.Saved -= MemberGroupService_Saved; MemberGroupService.Deleted -= MemberGroupService_Deleted; //Bind to media events - MediaService.Saved -= MediaServiceSaved; - MediaService.Deleted -= MediaServiceDeleted; - MediaService.Moved -= MediaServiceMoved; - MediaService.Trashed -= MediaServiceTrashed; - MediaService.EmptiedRecycleBin -= MediaServiceEmptiedRecycleBin; + MediaService.Saved -= MediaService_Saved; + MediaService.Deleted -= MediaService_Deleted; + MediaService.Moved -= MediaService_Moved; + MediaService.Trashed -= MediaService_Trashed; + MediaService.EmptiedRecycleBin -= MediaService_EmptiedRecycleBin; //Bind to content events - this is for unpublished content syncing across servers (primarily for examine) - ContentService.Saved -= ContentServiceSaved; - ContentService.Deleted -= ContentServiceDeleted; - ContentService.Copied -= ContentServiceCopied; + ContentService.Saved -= ContentService_Saved; + ContentService.Deleted -= ContentService_Deleted; + ContentService.Copied -= ContentService_Copied; //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; - ContentService.EmptiedRecycleBin -= ContentServiceEmptiedRecycleBin; + ContentService.Trashed -= ContentService_Trashed; + ContentService.EmptiedRecycleBin -= ContentService_EmptiedRecycleBin; - PublishingStrategy.Published -= PublishingStrategy_Published; - PublishingStrategy.UnPublished -= PublishingStrategy_UnPublished; + ContentService.Published -= ContentService_Published; + ContentService.UnPublished -= ContentService_UnPublished; //public access events PublicAccessService.Saved -= PublicAccessService_Saved; PublicAccessService.Deleted -= PublicAccessService_Deleted; - RelationService.SavedRelationType -= RelationType_Saved; - RelationService.DeletedRelationType -= RelationType_Deleted; + RelationService.SavedRelationType -= RelationService_SavedRelationType; + RelationService.DeletedRelationType -= RelationService_DeletedRelationType; } #region Publishing - void PublishingStrategy_UnPublished(IPublishingStrategy sender, PublishEventArgs e) + static void ContentService_UnPublished(IPublishingStrategy sender, PublishEventArgs e) { if (e.PublishedEntities.Any()) { @@ -263,12 +265,12 @@ namespace Umbraco.Web.Cache /// /// Refreshes the xml cache for a single node by removing it /// - private void UnPublishSingle(IContent content) + private static void UnPublishSingle(IContent content) { DistributedCache.Instance.RemovePageCache(content); } - void PublishingStrategy_Published(IPublishingStrategy sender, PublishEventArgs e) + static void ContentService_Published(IPublishingStrategy sender, PublishEventArgs e) { if (e.PublishedEntities.Any()) { @@ -293,7 +295,7 @@ namespace Umbraco.Web.Cache /// /// Refreshes the xml cache for all nodes /// - private void UpdateEntireCache() + private static void UpdateEntireCache() { DistributedCache.Instance.RefreshAllPageCache(); } @@ -301,7 +303,7 @@ namespace Umbraco.Web.Cache /// /// Refreshes the xml cache for nodes in list /// - private void UpdateMultipleContentCache(IEnumerable content) + private static void UpdateMultipleContentCache(IEnumerable content) { DistributedCache.Instance.RefreshPageCache(content.ToArray()); } @@ -309,7 +311,7 @@ namespace Umbraco.Web.Cache /// /// Refreshes the xml cache for a single node /// - private void UpdateSingleContentCache(IContent content) + private static void UpdateSingleContentCache(IContent content) { DistributedCache.Instance.RefreshPageCache(content); } @@ -323,7 +325,7 @@ namespace Umbraco.Web.Cache DistributedCache.Instance.RefreshPublicAccess(); } - private void PublicAccessService_Deleted(IPublicAccessService sender, DeleteEventArgs e) + static void PublicAccessService_Deleted(IPublicAccessService sender, DeleteEventArgs e) { DistributedCache.Instance.RefreshPublicAccess(); } @@ -332,7 +334,7 @@ namespace Umbraco.Web.Cache #region Content service event handlers - static void ContentServiceEmptiedRecycleBin(IContentService sender, RecycleBinEventArgs e) + static void ContentService_EmptiedRecycleBin(IContentService sender, RecycleBinEventArgs e) { if (e.RecycleBinEmptiedSuccessfully && e.IsContentRecycleBin) { @@ -349,7 +351,7 @@ namespace Umbraco.Web.Cache /// 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) + static void ContentService_Trashed(IContentService sender, MoveEventArgs e) { DistributedCache.Instance.RefreshUnpublishedPageCache( e.MoveInfoCollection.Select(x => x.Entity).ToArray()); @@ -364,7 +366,7 @@ namespace Umbraco.Web.Cache /// When an entity is copied new permissions may be assigned to it based on it's parent, if that is the /// case then we need to clear all user permissions cache. /// - static void ContentServiceCopied(IContentService sender, CopyEventArgs e) + static void ContentService_Copied(IContentService sender, CopyEventArgs e) { //check if permissions have changed var permissionsChanged = ((Content)e.Copy).WasPropertyDirty("PermissionsChanged"); @@ -382,7 +384,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void ContentServiceDeleted(IContentService sender, DeleteEventArgs e) + static void ContentService_Deleted(IContentService sender, DeleteEventArgs e) { DistributedCache.Instance.RemoveUnpublishedPageCache(e.DeletedEntities.ToArray()); } @@ -399,7 +401,7 @@ namespace Umbraco.Web.Cache /// When an entity is created new permissions may be assigned to it based on it's parent, if that is the /// case then we need to clear all user permissions cache. /// - static void ContentServiceSaved(IContentService sender, SaveEventArgs e) + static void ContentService_Saved(IContentService sender, SaveEventArgs e) { var clearUserPermissions = false; e.SavedEntities.ForEach(x => @@ -432,41 +434,41 @@ namespace Umbraco.Web.Cache #endregion #region ApplicationTree event handlers - static void ApplicationTreeNew(ApplicationTree sender, EventArgs e) + static void ApplicationTreeService_New(ApplicationTree sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationTreeCache(); } - static void ApplicationTreeUpdated(ApplicationTree sender, EventArgs e) + static void ApplicationTreeService_Updated(ApplicationTree sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationTreeCache(); } - static void ApplicationTreeDeleted(ApplicationTree sender, EventArgs e) + static void ApplicationTreeService_Deleted(ApplicationTree sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationTreeCache(); } #endregion #region Application event handlers - static void ApplicationNew(Section sender, EventArgs e) + static void SectionService_New(Section sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationCache(); } - static void ApplicationDeleted(Section sender, EventArgs e) + static void SectionService_Deleted(Section sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationCache(); } #endregion #region UserType event handlers - static void UserServiceDeletedUserType(IUserService sender, DeleteEventArgs e) + static void UserService_DeletedUserType(IUserService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserTypeCache(x.Id)); } - static void UserServiceSavedUserType(IUserService sender, SaveEventArgs e) + static void UserService_SavedUserType(IUserService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserTypeCache(x.Id)); } @@ -475,12 +477,12 @@ namespace Umbraco.Web.Cache #region Dictionary event handlers - static void LocalizationServiceSavedDictionaryItem(ILocalizationService sender, SaveEventArgs e) + static void LocalizationService_SavedDictionaryItem(ILocalizationService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDictionaryCache(x.Id)); } - static void LocalizationServiceDeletedDictionaryItem(ILocalizationService sender, DeleteEventArgs e) + static void LocalizationService_DeletedDictionaryItem(ILocalizationService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDictionaryCache(x.Id)); } @@ -488,12 +490,12 @@ namespace Umbraco.Web.Cache #endregion #region DataType event handlers - static void DataTypeServiceSaved(IDataTypeService sender, SaveEventArgs e) + static void DataTypeService_Saved(IDataTypeService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDataTypeCache(x)); } - static void DataTypeServiceDeleted(IDataTypeService sender, DeleteEventArgs e) + static void DataTypeService_Deleted(IDataTypeService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDataTypeCache(x)); } @@ -503,12 +505,12 @@ namespace Umbraco.Web.Cache #region Stylesheet and stylesheet property event handlers - static void FileServiceDeletedStylesheet(IFileService sender, DeleteEventArgs e) + static void FileService_DeletedStylesheet(IFileService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveStylesheetCache(x)); } - static void FileServiceSavedStylesheet(IFileService sender, SaveEventArgs e) + static void FileService_SavedStylesheet(IFileService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshStylesheetCache(x)); } @@ -535,7 +537,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void LocalizationServiceDeletedLanguage(ILocalizationService sender, DeleteEventArgs e) + static void LocalizationService_DeletedLanguage(ILocalizationService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveLanguageCache(x)); } @@ -545,7 +547,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void LocalizationServiceSavedLanguage(ILocalizationService sender, SaveEventArgs e) + static void LocalizationService_SavedLanguage(ILocalizationService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshLanguageCache(x)); } @@ -558,7 +560,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void ContentTypeServiceDeletedMediaType(IContentTypeService sender, DeleteEventArgs e) + static void ContentTypeService_DeletedMediaType(IContentTypeService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveMediaTypeCache(x)); } @@ -568,7 +570,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void ContentTypeServiceDeletedContentType(IContentTypeService sender, DeleteEventArgs e) + static void ContentTypeService_DeletedContentType(IContentTypeService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(contentType => DistributedCache.Instance.RemoveContentTypeCache(contentType)); } @@ -578,7 +580,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void MemberTypeServiceDeleted(IMemberTypeService sender, DeleteEventArgs e) + static void MemberTypeService_Deleted(IMemberTypeService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(contentType => DistributedCache.Instance.RemoveMemberTypeCache(contentType)); } @@ -588,7 +590,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void ContentTypeServiceSavedMediaType(IContentTypeService sender, SaveEventArgs e) + static void ContentTypeService_SavedMediaType(IContentTypeService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshMediaTypeCache(x)); } @@ -598,7 +600,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void ContentTypeServiceSavedContentType(IContentTypeService sender, SaveEventArgs e) + static void ContentTypeService_SavedContentType(IContentTypeService sender, SaveEventArgs e) { e.SavedEntities.ForEach(contentType => DistributedCache.Instance.RefreshContentTypeCache(contentType)); } @@ -608,7 +610,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void MemberTypeServiceSaved(IMemberTypeService sender, SaveEventArgs e) + static void MemberTypeService_Saved(IMemberTypeService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshMemberTypeCache(x)); } @@ -617,7 +619,8 @@ namespace Umbraco.Web.Cache #endregion #region User/permissions event handlers - + + //fixme: this isn't named correct static void CacheRefresherEventHandler_AssignedPermissions(PermissionRepository sender, SaveEventArgs e) { var userIds = e.SavedEntities.Select(x => x.UserId).Distinct(); @@ -639,12 +642,12 @@ namespace Umbraco.Web.Cache InvalidateCacheForPermissionsChange(sender); } - static void UserServiceSavedUser(IUserService sender, SaveEventArgs e) + static void UserService_SavedUser(IUserService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserCache(x.Id)); } - static void UserServiceDeletedUser(IUserService sender, DeleteEventArgs e) + static void UserService_DeletedUser(IUserService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserCache(x.Id)); } @@ -674,7 +677,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void FileServiceDeletedTemplate(IFileService sender, DeleteEventArgs e) + static void FileService_DeletedTemplate(IFileService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveTemplateCache(x.Id)); } @@ -684,7 +687,7 @@ namespace Umbraco.Web.Cache /// /// /// - static void FileServiceSavedTemplate(IFileService sender, SaveEventArgs e) + static void FileService_SavedTemplate(IFileService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshTemplateCache(x.Id)); } @@ -693,7 +696,7 @@ namespace Umbraco.Web.Cache #region Macro event handlers - void MacroServiceDeleted(IMacroService sender, DeleteEventArgs e) + static void MacroService_Deleted(IMacroService sender, DeleteEventArgs e) { foreach (var entity in e.DeletedEntities) { @@ -701,7 +704,7 @@ namespace Umbraco.Web.Cache } } - void MacroServiceSaved(IMacroService sender, SaveEventArgs e) + static void MacroService_Saved(IMacroService sender, SaveEventArgs e) { foreach (var entity in e.SavedEntities) { @@ -713,7 +716,7 @@ namespace Umbraco.Web.Cache #region Media event handlers - static void MediaServiceEmptiedRecycleBin(IMediaService sender, RecycleBinEventArgs e) + static void MediaService_EmptiedRecycleBin(IMediaService sender, RecycleBinEventArgs e) { if (e.RecycleBinEmptiedSuccessfully && e.IsMediaRecycleBin) { @@ -721,22 +724,22 @@ namespace Umbraco.Web.Cache } } - static void MediaServiceTrashed(IMediaService sender, MoveEventArgs e) + static void MediaService_Trashed(IMediaService sender, MoveEventArgs e) { DistributedCache.Instance.RemoveMediaCacheAfterRecycling(e.MoveInfoCollection.ToArray()); } - static void MediaServiceMoved(IMediaService sender, MoveEventArgs e) + static void MediaService_Moved(IMediaService sender, MoveEventArgs e) { DistributedCache.Instance.RefreshMediaCacheAfterMoving(e.MoveInfoCollection.ToArray()); } - static void MediaServiceDeleted(IMediaService sender, DeleteEventArgs e) + static void MediaService_Deleted(IMediaService sender, DeleteEventArgs e) { DistributedCache.Instance.RemoveMediaCachePermanently(e.DeletedEntities.Select(x => x.Id).ToArray()); } - static void MediaServiceSaved(IMediaService sender, SaveEventArgs e) + static void MediaService_Saved(IMediaService sender, SaveEventArgs e) { DistributedCache.Instance.RefreshMediaCache(e.SavedEntities.ToArray()); } @@ -744,12 +747,12 @@ namespace Umbraco.Web.Cache #region Member event handlers - static void MemberServiceDeleted(IMemberService sender, DeleteEventArgs e) + static void MemberService_Deleted(IMemberService sender, DeleteEventArgs e) { DistributedCache.Instance.RemoveMemberCache(e.DeletedEntities.ToArray()); } - static void MemberServiceSaved(IMemberService sender, SaveEventArgs e) + static void MemberService_Saved(IMemberService sender, SaveEventArgs e) { DistributedCache.Instance.RefreshMemberCache(e.SavedEntities.ToArray()); } @@ -777,14 +780,14 @@ namespace Umbraco.Web.Cache #region Relation type event handlers - private static void RelationType_Saved(IRelationService sender, SaveEventArgs args) + static void RelationService_SavedRelationType(IRelationService sender, SaveEventArgs args) { var dc = DistributedCache.Instance; foreach (var e in args.SavedEntities) dc.RefreshRelationTypeCache(e.Id); } - private static void RelationType_Deleted(IRelationService sender, DeleteEventArgs args) + static void RelationService_DeletedRelationType(IRelationService sender, DeleteEventArgs args) { var dc = DistributedCache.Instance; foreach (var e in args.DeletedEntities) @@ -792,5 +795,60 @@ namespace Umbraco.Web.Cache } #endregion + + + /// + /// This will inspect the event metadata and execute it's affiliated handler if one is found + /// + /// + internal static void HandleEvents(IEnumerable events) + { + foreach (var e in events) + { + var handler = FindHandler(e); + if (handler == null) continue; + + handler.Invoke(null, new object[] { e.Sender, e.Args }); + } + } + + /// + /// Used to cache all candidate handlers + /// + private static readonly Lazy CandidateHandlers = new Lazy(() => + { + var candidates = + + typeof(CacheRefresherEventHandler).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) + .Where(x => x.Name.Contains("_")) + .Select(x => new + { + method = x, + nameParts = x.Name.Split(new[] { "_" }, StringSplitOptions.RemoveEmptyEntries), + methodParams = x.GetParameters() + }) + .Where(x => x.nameParts.Length == 2 && x.methodParams.Length == 2 && typeof(EventArgs).IsAssignableFrom(x.methodParams[1].ParameterType)) + .Select(x => x.method) + .ToArray(); + + return candidates; + }); + + /// + /// Used to cache all found event handlers + /// + private static readonly ConcurrentDictionary FoundHandlers = new ConcurrentDictionary(); + + internal static MethodInfo FindHandler(IEventDefinition eventDefinition) + { + return FoundHandlers.GetOrAdd(eventDefinition, definition => + { + var candidates = CandidateHandlers.Value; + + var found = candidates.FirstOrDefault(x => x.Name == string.Format("{0}_{1}", eventDefinition.Sender.GetType().Name, eventDefinition.EventName)); + + return found; + }); + } } } \ No newline at end of file From a65ad3c72a517ca31bc64ffed7ae3997cf7a29ea Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 6 Feb 2017 18:46:23 +1100 Subject: [PATCH 3/3] Adds equality members for all event args, updates equality check for EventDefinitionBase to include the args (since that is quite important!), creates an OrderedHashSet class which can deduplicate lists for us based on member equality and supports first in or last in + tests. --- .../Events/CancellableEventArgs.cs | 38 ++++++++- .../Events/CancellableObjectEventArgs.cs | 35 +++++++- src/Umbraco.Core/Events/CopyEventArgs.cs | 42 +++++++++- src/Umbraco.Core/Events/DeleteEventArgs.cs | 73 ++++++++++++++++- .../Events/DeleteRevisionsEventArgs.cs | 39 ++++++++- .../Events/EventDefinitionBase.cs | 9 ++- .../Events/EventDefinitionFilter.cs | 24 ++++++ src/Umbraco.Core/Events/ExportEventArgs.cs | 38 ++++++++- src/Umbraco.Core/Events/IEventDispatcher.cs | 2 +- src/Umbraco.Core/Events/ImportEventArgs.cs | 38 ++++++++- .../Events/ImportPackageEventArgs.cs | 38 ++++++++- src/Umbraco.Core/Events/MigrationEventArgs.cs | 40 ++++++++- src/Umbraco.Core/Events/MoveEventArgs.cs | 35 +++++++- src/Umbraco.Core/Events/MoveEventInfo.cs | 41 +++++++++- src/Umbraco.Core/Events/NewEventArgs.cs | 41 +++++++++- .../Events/PassThroughEventDispatcher.cs | 2 +- src/Umbraco.Core/Events/PublishEventArgs.cs | 38 ++++++++- .../Events/RecycleBinEventArgs.cs | 41 +++++++++- src/Umbraco.Core/Events/RollbackEventArgs.cs | 2 + src/Umbraco.Core/Events/SaveEventArgs.cs | 2 + .../Events/ScopeEventDispatcher.cs | 35 ++++++-- src/Umbraco.Core/OrderedHashSet.cs | 50 ++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 2 + .../Collections/OrderedHashSetTests.cs | 81 +++++++++++++++++++ .../PassThroughEventDispatcherTests.cs | 2 +- .../Scoping/ScopeEventDispatcherTests.cs | 10 +-- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 27 files changed, 760 insertions(+), 39 deletions(-) create mode 100644 src/Umbraco.Core/Events/EventDefinitionFilter.cs create mode 100644 src/Umbraco.Core/OrderedHashSet.cs create mode 100644 src/Umbraco.Tests/Collections/OrderedHashSetTests.cs diff --git a/src/Umbraco.Core/Events/CancellableEventArgs.cs b/src/Umbraco.Core/Events/CancellableEventArgs.cs index 72cef19f7a..a102ea66ef 100644 --- a/src/Umbraco.Core/Events/CancellableEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableEventArgs.cs @@ -10,8 +10,8 @@ namespace Umbraco.Core.Events /// Event args for that can support cancellation /// [HostProtection(SecurityAction.LinkDemand, SharedState = true)] - public class CancellableEventArgs : EventArgs - { + public class CancellableEventArgs : EventArgs, IEquatable + { private bool _cancel; public CancellableEventArgs(bool canCancel, EventMessages messages, IDictionary additionalData) @@ -98,6 +98,36 @@ namespace Umbraco.Core.Events /// This allows for a bit of flexibility in our event raising - it's not pretty but we need to maintain backwards compatibility /// so we cannot change the strongly typed nature for some events. /// - public ReadOnlyDictionary AdditionalData { get; private set; } - } + public ReadOnlyDictionary AdditionalData { get; private set; } + + public bool Equals(CancellableEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(AdditionalData, other.AdditionalData); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CancellableEventArgs) obj); + } + + public override int GetHashCode() + { + return (AdditionalData != null ? AdditionalData.GetHashCode() : 0); + } + + public static bool operator ==(CancellableEventArgs left, CancellableEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(CancellableEventArgs left, CancellableEventArgs right) + { + return !Equals(left, right); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs index a05f09ece5..7815747f18 100644 --- a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Security.Permissions; using Umbraco.Core.Models; @@ -10,7 +11,7 @@ namespace Umbraco.Core.Events /// /// [HostProtection(SecurityAction.LinkDemand, SharedState = true)] - public class CancellableObjectEventArgs : CancellableEventArgs + public class CancellableObjectEventArgs : CancellableEventArgs, IEquatable> { public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) : base(canCancel, messages, additionalData) @@ -48,5 +49,37 @@ namespace Umbraco.Core.Events /// protected T EventObject { get; set; } + public bool Equals(CancellableObjectEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && EqualityComparer.Default.Equals(EventObject, other.EventObject); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CancellableObjectEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(EventObject); + } + } + + public static bool operator ==(CancellableObjectEventArgs left, CancellableObjectEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(CancellableObjectEventArgs left, CancellableObjectEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/CopyEventArgs.cs b/src/Umbraco.Core/Events/CopyEventArgs.cs index 16f4fae982..65dc54cc88 100644 --- a/src/Umbraco.Core/Events/CopyEventArgs.cs +++ b/src/Umbraco.Core/Events/CopyEventArgs.cs @@ -1,6 +1,9 @@ +using System; +using System.Collections.Generic; + namespace Umbraco.Core.Events { - public class CopyEventArgs : CancellableObjectEventArgs + public class CopyEventArgs : CancellableObjectEventArgs, IEquatable> { public CopyEventArgs(TEntity original, TEntity copy, bool canCancel, int parentId) : base(original, canCancel) @@ -43,5 +46,42 @@ namespace Umbraco.Core.Events public int ParentId { get; private set; } public bool RelateToOriginal { get; set; } + + public bool Equals(CopyEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && EqualityComparer.Default.Equals(Copy, other.Copy) && ParentId == other.ParentId && RelateToOriginal == other.RelateToOriginal; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CopyEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Copy); + hashCode = (hashCode * 397) ^ ParentId; + hashCode = (hashCode * 397) ^ RelateToOriginal.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(CopyEventArgs left, CopyEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(CopyEventArgs left, CopyEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs index 1025066bcc..8a0fdaf290 100644 --- a/src/Umbraco.Core/Events/DeleteEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteEventArgs.cs @@ -1,8 +1,9 @@ +using System; using System.Collections.Generic; namespace Umbraco.Core.Events { - public class DeleteEventArgs : CancellableObjectEventArgs> + public class DeleteEventArgs : CancellableObjectEventArgs>, IEquatable> { /// /// Constructor accepting multiple entities that are used in the delete operation @@ -99,10 +100,43 @@ namespace Umbraco.Core.Events /// /// A list of media files that can be added to during a deleted operation for which Umbraco will ensure are removed /// - public List MediaFilesToDelete { get; private set; } + public List MediaFilesToDelete { get; private set; } + + public bool Equals(DeleteEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && MediaFilesToDelete.Equals(other.MediaFilesToDelete); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((DeleteEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ MediaFilesToDelete.GetHashCode(); + } + } + + public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) + { + return !Equals(left, right); + } } - public class DeleteEventArgs : CancellableEventArgs + public class DeleteEventArgs : CancellableEventArgs, IEquatable { public DeleteEventArgs(int id, bool canCancel, EventMessages eventMessages) : base(canCancel, eventMessages) @@ -125,5 +159,38 @@ namespace Umbraco.Core.Events /// Gets the Id of the object being deleted. /// public int Id { get; private set; } + + public bool Equals(DeleteEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && Id == other.Id; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((DeleteEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ Id; + } + } + + public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs b/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs index 4d53270608..1db1296640 100644 --- a/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs @@ -2,7 +2,7 @@ using System; namespace Umbraco.Core.Events { - public class DeleteRevisionsEventArgs : DeleteEventArgs + public class DeleteRevisionsEventArgs : DeleteEventArgs, IEquatable { public DeleteRevisionsEventArgs(int id, bool canCancel, Guid specificVersion = default(Guid), bool deletePriorVersions = false, DateTime dateToRetain = default(DateTime)) : base(id, canCancel) @@ -31,5 +31,42 @@ namespace Umbraco.Core.Events { get { return SpecificVersion != default(Guid); } } + + public bool Equals(DeleteRevisionsEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && DateToRetain.Equals(other.DateToRetain) && DeletePriorVersions == other.DeletePriorVersions && SpecificVersion.Equals(other.SpecificVersion); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((DeleteRevisionsEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ DateToRetain.GetHashCode(); + hashCode = (hashCode * 397) ^ DeletePriorVersions.GetHashCode(); + hashCode = (hashCode * 397) ^ SpecificVersion.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/EventDefinitionBase.cs b/src/Umbraco.Core/Events/EventDefinitionBase.cs index bb107ea09c..c0a061f80f 100644 --- a/src/Umbraco.Core/Events/EventDefinitionBase.cs +++ b/src/Umbraco.Core/Events/EventDefinitionBase.cs @@ -35,7 +35,7 @@ namespace Umbraco.Core.Events { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Sender.Equals(other.Sender) && string.Equals(EventName, other.EventName); + return Args.Equals(other.Args) && string.Equals(EventName, other.EventName) && Sender.Equals(other.Sender); } public override bool Equals(object obj) @@ -50,7 +50,10 @@ namespace Umbraco.Core.Events { unchecked { - return (Sender.GetHashCode() * 397) ^ EventName.GetHashCode(); + var hashCode = Args.GetHashCode(); + hashCode = (hashCode * 397) ^ EventName.GetHashCode(); + hashCode = (hashCode * 397) ^ Sender.GetHashCode(); + return hashCode; } } @@ -61,7 +64,7 @@ namespace Umbraco.Core.Events public static bool operator !=(EventDefinitionBase left, EventDefinitionBase right) { - return !Equals(left, right); + return Equals(left, right) == false; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/EventDefinitionFilter.cs b/src/Umbraco.Core/Events/EventDefinitionFilter.cs new file mode 100644 index 0000000000..4bbe75d10b --- /dev/null +++ b/src/Umbraco.Core/Events/EventDefinitionFilter.cs @@ -0,0 +1,24 @@ +namespace Umbraco.Core.Events +{ + /// + /// The filter used in the GetEvents method which determines + /// how the result list is filtered + /// + public enum EventDefinitionFilter + { + /// + /// Returns all events tracked + /// + All, + + /// + /// Deduplicates events and only returns the first duplicate instance tracked + /// + FirstIn, + + /// + /// Deduplicates events and only returns the last duplicate instance tracked + /// + LastIn + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ExportEventArgs.cs b/src/Umbraco.Core/Events/ExportEventArgs.cs index 161a073615..acbf920636 100644 --- a/src/Umbraco.Core/Events/ExportEventArgs.cs +++ b/src/Umbraco.Core/Events/ExportEventArgs.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Xml.Linq; namespace Umbraco.Core.Events { - public class ExportEventArgs : CancellableObjectEventArgs> + public class ExportEventArgs : CancellableObjectEventArgs>, IEquatable> { /// /// Constructor accepting a single entity instance @@ -48,5 +49,38 @@ namespace Umbraco.Core.Events /// Returns the xml relating to the export event /// public XElement Xml { get; private set; } + + public bool Equals(ExportEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && Equals(Xml, other.Xml); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ExportEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (Xml != null ? Xml.GetHashCode() : 0); + } + } + + public static bool operator ==(ExportEventArgs left, ExportEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(ExportEventArgs left, ExportEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/IEventDispatcher.cs b/src/Umbraco.Core/Events/IEventDispatcher.cs index c5daf6b2f0..78f8ed3a4a 100644 --- a/src/Umbraco.Core/Events/IEventDispatcher.cs +++ b/src/Umbraco.Core/Events/IEventDispatcher.cs @@ -93,6 +93,6 @@ namespace Umbraco.Core.Events /// Gets the collected events. /// /// The collected events. - IEnumerable GetEvents(); + IEnumerable GetEvents(EventDefinitionFilter filter); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ImportEventArgs.cs b/src/Umbraco.Core/Events/ImportEventArgs.cs index 3bdd6d6fcf..dcecf5c36b 100644 --- a/src/Umbraco.Core/Events/ImportEventArgs.cs +++ b/src/Umbraco.Core/Events/ImportEventArgs.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Xml.Linq; namespace Umbraco.Core.Events { - public class ImportEventArgs : CancellableObjectEventArgs> + public class ImportEventArgs : CancellableObjectEventArgs>, IEquatable> { /// /// Constructor accepting an XElement with the xml being imported @@ -46,5 +47,38 @@ namespace Umbraco.Core.Events /// Returns the xml relating to the import event /// public XElement Xml { get; private set; } + + public bool Equals(ImportEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && Equals(Xml, other.Xml); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ImportEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (Xml != null ? Xml.GetHashCode() : 0); + } + } + + public static bool operator ==(ImportEventArgs left, ImportEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(ImportEventArgs left, ImportEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs index 8596629731..506481a79e 100644 --- a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs +++ b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Packaging.Models; namespace Umbraco.Core.Events { - internal class ImportPackageEventArgs : CancellableObjectEventArgs> + internal class ImportPackageEventArgs : CancellableObjectEventArgs>, IEquatable> { private readonly MetaData _packageMetaData; @@ -22,5 +23,38 @@ namespace Umbraco.Core.Events { get { return _packageMetaData; } } + + public bool Equals(ImportPackageEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && _packageMetaData.Equals(other._packageMetaData); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ImportPackageEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ _packageMetaData.GetHashCode(); + } + } + + public static bool operator ==(ImportPackageEventArgs left, ImportPackageEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(ImportPackageEventArgs left, ImportPackageEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/MigrationEventArgs.cs b/src/Umbraco.Core/Events/MigrationEventArgs.cs index 6afe9bd754..8b8898e7d4 100644 --- a/src/Umbraco.Core/Events/MigrationEventArgs.cs +++ b/src/Umbraco.Core/Events/MigrationEventArgs.cs @@ -7,7 +7,7 @@ using Umbraco.Core.Persistence.Migrations; namespace Umbraco.Core.Events { - public class MigrationEventArgs : CancellableObjectEventArgs> + public class MigrationEventArgs : CancellableObjectEventArgs>, IEquatable { /// /// Constructor accepting multiple migrations that are used in the migration runner @@ -141,5 +141,43 @@ namespace Umbraco.Core.Events public string ProductName { get; private set; } internal MigrationContext MigrationContext { get; private set; } + + public bool Equals(MigrationEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && ConfiguredSemVersion.Equals(other.ConfiguredSemVersion) && MigrationContext.Equals(other.MigrationContext) && string.Equals(ProductName, other.ProductName) && TargetSemVersion.Equals(other.TargetSemVersion); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((MigrationEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ ConfiguredSemVersion.GetHashCode(); + hashCode = (hashCode * 397) ^ MigrationContext.GetHashCode(); + hashCode = (hashCode * 397) ^ ProductName.GetHashCode(); + hashCode = (hashCode * 397) ^ TargetSemVersion.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(MigrationEventArgs left, MigrationEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(MigrationEventArgs left, MigrationEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/MoveEventArgs.cs b/src/Umbraco.Core/Events/MoveEventArgs.cs index 0f0a5183a9..228e1ca2f7 100644 --- a/src/Umbraco.Core/Events/MoveEventArgs.cs +++ b/src/Umbraco.Core/Events/MoveEventArgs.cs @@ -4,7 +4,7 @@ using System.Linq; namespace Umbraco.Core.Events { - public class MoveEventArgs : CancellableObjectEventArgs + public class MoveEventArgs : CancellableObjectEventArgs, IEquatable> { /// /// Constructor accepting a collection of MoveEventInfo objects @@ -123,5 +123,38 @@ namespace Umbraco.Core.Events /// [Obsolete("Retrieve the ParentId from the MoveInfoCollection property instead")] public int ParentId { get; private set; } + + public bool Equals(MoveEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && MoveInfoCollection.Equals(other.MoveInfoCollection); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((MoveEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ MoveInfoCollection.GetHashCode(); + } + } + + public static bool operator ==(MoveEventArgs left, MoveEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(MoveEventArgs left, MoveEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/MoveEventInfo.cs b/src/Umbraco.Core/Events/MoveEventInfo.cs index a74db7f36e..9e77971837 100644 --- a/src/Umbraco.Core/Events/MoveEventInfo.cs +++ b/src/Umbraco.Core/Events/MoveEventInfo.cs @@ -1,6 +1,9 @@ +using System; +using System.Collections.Generic; + namespace Umbraco.Core.Events { - public class MoveEventInfo + public class MoveEventInfo : IEquatable> { public MoveEventInfo(TEntity entity, string originalPath, int newParentId) { @@ -12,5 +15,41 @@ namespace Umbraco.Core.Events public TEntity Entity { get; set; } public string OriginalPath { get; set; } public int NewParentId { get; set; } + + public bool Equals(MoveEventInfo other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return EqualityComparer.Default.Equals(Entity, other.Entity) && NewParentId == other.NewParentId && string.Equals(OriginalPath, other.OriginalPath); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((MoveEventInfo) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = EqualityComparer.Default.GetHashCode(Entity); + hashCode = (hashCode * 397) ^ NewParentId; + hashCode = (hashCode * 397) ^ OriginalPath.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(MoveEventInfo left, MoveEventInfo right) + { + return Equals(left, right); + } + + public static bool operator !=(MoveEventInfo left, MoveEventInfo right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/NewEventArgs.cs b/src/Umbraco.Core/Events/NewEventArgs.cs index acfd64e60d..415d82b90e 100644 --- a/src/Umbraco.Core/Events/NewEventArgs.cs +++ b/src/Umbraco.Core/Events/NewEventArgs.cs @@ -1,8 +1,10 @@ +using System; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Events { - public class NewEventArgs : CancellableObjectEventArgs + public class NewEventArgs : CancellableObjectEventArgs, IEquatable> { @@ -84,5 +86,42 @@ namespace Umbraco.Core.Events /// Gets or Sets the parent IContent object. /// public TEntity Parent { get; private set; } + + public bool Equals(NewEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && string.Equals(Alias, other.Alias) && EqualityComparer.Default.Equals(Parent, other.Parent) && ParentId == other.ParentId; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((NewEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ Alias.GetHashCode(); + hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Parent); + hashCode = (hashCode * 397) ^ ParentId; + return hashCode; + } + } + + public static bool operator ==(NewEventArgs left, NewEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(NewEventArgs left, NewEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs b/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs index b2b51f9361..1e1a9bc6d1 100644 --- a/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs +++ b/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs @@ -50,7 +50,7 @@ namespace Umbraco.Core.Events eventHandler(sender, args); } - public IEnumerable GetEvents() + public IEnumerable GetEvents(EventDefinitionFilter filter) { return Enumerable.Empty(); } diff --git a/src/Umbraco.Core/Events/PublishEventArgs.cs b/src/Umbraco.Core/Events/PublishEventArgs.cs index a791781617..1aa7c2308c 100644 --- a/src/Umbraco.Core/Events/PublishEventArgs.cs +++ b/src/Umbraco.Core/Events/PublishEventArgs.cs @@ -1,8 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Umbraco.Core.Events { - public class PublishEventArgs : CancellableObjectEventArgs> + public class PublishEventArgs : CancellableObjectEventArgs>, IEquatable> { /// /// Constructor accepting multiple entities that are used in the publish operation @@ -101,5 +102,38 @@ namespace Umbraco.Core.Events } public bool IsAllRepublished { get; private set; } + + public bool Equals(PublishEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && IsAllRepublished == other.IsAllRepublished; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((PublishEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ IsAllRepublished.GetHashCode(); + } + } + + public static bool operator ==(PublishEventArgs left, PublishEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(PublishEventArgs left, PublishEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs index ca4bbd2719..c6049cb7a6 100644 --- a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs +++ b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Events { - public class RecycleBinEventArgs : CancellableEventArgs + public class RecycleBinEventArgs : CancellableEventArgs, IEquatable { public RecycleBinEventArgs(Guid nodeObjectType, Dictionary> allPropertyData, bool emptiedSuccessfully) : base(false) @@ -122,5 +122,44 @@ namespace Umbraco.Core.Events { get { return NodeObjectType == new Guid(Constants.ObjectTypes.Media); } } + + public bool Equals(RecycleBinEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && AllPropertyData.Equals(other.AllPropertyData) && Files.Equals(other.Files) && Ids.Equals(other.Ids) && NodeObjectType.Equals(other.NodeObjectType) && RecycleBinEmptiedSuccessfully == other.RecycleBinEmptiedSuccessfully; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((RecycleBinEventArgs) obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ AllPropertyData.GetHashCode(); + hashCode = (hashCode * 397) ^ Files.GetHashCode(); + hashCode = (hashCode * 397) ^ Ids.GetHashCode(); + hashCode = (hashCode * 397) ^ NodeObjectType.GetHashCode(); + hashCode = (hashCode * 397) ^ RecycleBinEmptiedSuccessfully.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(RecycleBinEventArgs left, RecycleBinEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(RecycleBinEventArgs left, RecycleBinEventArgs right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/RollbackEventArgs.cs b/src/Umbraco.Core/Events/RollbackEventArgs.cs index db9dded08c..cf2189e962 100644 --- a/src/Umbraco.Core/Events/RollbackEventArgs.cs +++ b/src/Umbraco.Core/Events/RollbackEventArgs.cs @@ -17,5 +17,7 @@ namespace Umbraco.Core.Events { get { return EventObject; } } + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/SaveEventArgs.cs b/src/Umbraco.Core/Events/SaveEventArgs.cs index dafd326e1c..e816a8f8bd 100644 --- a/src/Umbraco.Core/Events/SaveEventArgs.cs +++ b/src/Umbraco.Core/Events/SaveEventArgs.cs @@ -116,5 +116,7 @@ namespace Umbraco.Core.Events { get { return EventObject; } } + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ScopeEventDispatcher.cs b/src/Umbraco.Core/Events/ScopeEventDispatcher.cs index 8073b9ac6e..6eb6ee3b85 100644 --- a/src/Umbraco.Core/Events/ScopeEventDispatcher.cs +++ b/src/Umbraco.Core/Events/ScopeEventDispatcher.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; namespace Umbraco.Core.Events -{ +{ + /// /// This event manager is created for each scope and is aware of if it is nested in an outer scope /// @@ -13,14 +15,14 @@ namespace Umbraco.Core.Events internal class ScopeEventDispatcher : IEventDispatcher { private readonly EventsDispatchMode _mode; - private List _events; + private List _events; public ScopeEventDispatcher(EventsDispatchMode mode) { _mode = mode; } - private List Events { get { return _events ?? (_events = new List()); } } + private List Events { get { return _events ?? (_events = new List()); } } private bool PassThroughCancelable { get { return _mode == EventsDispatchMode.PassThrough || _mode == EventsDispatchMode.Scope; } } @@ -81,9 +83,32 @@ namespace Umbraco.Core.Events Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); } - public IEnumerable GetEvents() + public IEnumerable GetEvents(EventDefinitionFilter filter) { - return _events ?? Enumerable.Empty(); + if (_events == null) + return Enumerable.Empty(); + + switch (filter) + { + case EventDefinitionFilter.All: + return _events; + case EventDefinitionFilter.FirstIn: + var l1 = new OrderedHashSet(); + foreach (var e in _events) + { + l1.Add(e); + } + return l1; + case EventDefinitionFilter.LastIn: + var l2 = new OrderedHashSet(keepOldest:false); + foreach (var e in _events) + { + l2.Add(e); + } + return l2; + default: + throw new ArgumentOutOfRangeException("filter", filter, null); + } } public void ScopeExit(bool completed) diff --git a/src/Umbraco.Core/OrderedHashSet.cs b/src/Umbraco.Core/OrderedHashSet.cs new file mode 100644 index 0000000000..2fd545c915 --- /dev/null +++ b/src/Umbraco.Core/OrderedHashSet.cs @@ -0,0 +1,50 @@ +using System.Collections.ObjectModel; + +namespace Umbraco.Core +{ + /// + /// A custom collection similar to HashSet{T} which only contains unique items, however this collection keeps items in order + /// and is customizable to keep the newest or oldest equatable item + /// + /// + internal class OrderedHashSet : KeyedCollection + { + private readonly bool _keepOldest; + + public OrderedHashSet(bool keepOldest = true) + { + _keepOldest = keepOldest; + } + + protected override void InsertItem(int index, T item) + { + if (Dictionary == null) + { + base.InsertItem(index, item); + } + else + { + var exists = Dictionary.ContainsKey(item); + + //if we want to keep the newest, then we need to remove the old item and add the new one + if (exists == false) + { + base.InsertItem(index, item); + } + else if(_keepOldest == false) + { + if (Remove(item)) + { + index--; + } + base.InsertItem(index, item); + } + } + } + + protected override T GetKeyForItem(T item) + { + return item; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5da637f1ee..bde8a47169 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -311,6 +311,8 @@ + + diff --git a/src/Umbraco.Tests/Collections/OrderedHashSetTests.cs b/src/Umbraco.Tests/Collections/OrderedHashSetTests.cs new file mode 100644 index 0000000000..267e31506f --- /dev/null +++ b/src/Umbraco.Tests/Collections/OrderedHashSetTests.cs @@ -0,0 +1,81 @@ +using System; +using NUnit.Framework; +using Umbraco.Core; + +namespace Umbraco.Tests.Collections +{ + [TestFixture] + public class OrderedHashSetTests + { + [Test] + public void Keeps_Last() + { + var list = new OrderedHashSet(keepOldest:false); + var items = new MyClass[] {new MyClass("test"), new MyClass("test"), new MyClass("test") }; + foreach (var item in items) + { + list.Add(item); + } + + Assert.AreEqual(1, list.Count); + Assert.AreEqual(items[2].Id, list[0].Id); + Assert.AreNotEqual(items[0].Id, list[0].Id); + } + + [Test] + public void Keeps_First() + { + var list = new OrderedHashSet(keepOldest: true); + var items = new MyClass[] { new MyClass("test"), new MyClass("test"), new MyClass("test") }; + foreach (var item in items) + { + list.Add(item); + } + + Assert.AreEqual(1, list.Count); + Assert.AreEqual(items[0].Id, list[0].Id); + } + + private class MyClass : IEquatable + { + public MyClass(string name) + { + Name = name; + Id = Guid.NewGuid(); + } + + public string Name { get; private set; } + public Guid Id { get; private set; } + + public bool Equals(MyClass other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Name, other.Name); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((MyClass) obj); + } + + public override int GetHashCode() + { + return Name.GetHashCode(); + } + + public static bool operator ==(MyClass left, MyClass right) + { + return Equals(left, right); + } + + public static bool operator !=(MyClass left, MyClass right) + { + return !Equals(left, right); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Scoping/PassThroughEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/PassThroughEventDispatcherTests.cs index 6020d1f888..c8bb1137d3 100644 --- a/src/Umbraco.Tests/Scoping/PassThroughEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/PassThroughEventDispatcherTests.cs @@ -52,7 +52,7 @@ namespace Umbraco.Tests.Scoping events.Dispatch(DoThing2, this, new EventArgs()); events.Dispatch(DoThing3, this, new EventArgs()); - Assert.IsEmpty(events.GetEvents()); + Assert.IsEmpty(events.GetEvents(EventDefinitionFilter.All)); } public event EventHandler DoThing1; diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs index 23921182ad..7d5754529e 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs @@ -77,9 +77,9 @@ namespace Umbraco.Tests.Scoping scope.Events.Dispatch(DoThing3, this, new SaveEventArgs(0)); // events have been queued - Assert.AreEqual(3, scope.Events.GetEvents().Count()); + Assert.AreEqual(3, scope.Events.GetEvents(EventDefinitionFilter.All).Count()); - var events = scope.Events.GetEvents().ToArray(); + var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray(); var knownNames = new[] { "DoThing1", "DoThing2", "DoThing3" }; var knownArgTypes = new[] { typeof (SaveEventArgs), typeof (SaveEventArgs), typeof (SaveEventArgs) }; @@ -110,7 +110,7 @@ namespace Umbraco.Tests.Scoping scope.Events.Dispatch(DoThing3, this, new SaveEventArgs(0)); // events have not been queued - Assert.IsEmpty(scope.Events.GetEvents()); + Assert.IsEmpty(scope.Events.GetEvents(EventDefinitionFilter.All)); // events have been raised Assert.AreEqual(3, counter); @@ -139,7 +139,7 @@ namespace Umbraco.Tests.Scoping scope.Events.Dispatch(DoThing3, this, new SaveEventArgs(0)); // events have been queued - Assert.AreEqual(3, scope.Events.GetEvents().Count()); + Assert.AreEqual(3, scope.Events.GetEvents(EventDefinitionFilter.All).Count()); if (complete) scope.Complete(); @@ -180,7 +180,7 @@ namespace Umbraco.Tests.Scoping scope.Events.Dispatch(DoThing3, this, new SaveEventArgs(0)); // events have been queued - Assert.AreEqual(3, scope.Events.GetEvents().Count()); + Assert.AreEqual(3, scope.Events.GetEvents(EventDefinitionFilter.All).Count()); if (complete) scope.Complete(); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 46c8387de8..e99dfaf454 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -158,6 +158,7 @@ +