diff --git a/uSync.BackOffice/Extensions/uSyncActionExtensions.cs b/uSync.BackOffice/Extensions/uSyncActionExtensions.cs index ef0eb673..3bb0b16a 100644 --- a/uSync.BackOffice/Extensions/uSyncActionExtensions.cs +++ b/uSync.BackOffice/Extensions/uSyncActionExtensions.cs @@ -25,7 +25,7 @@ public static bool ContainsErrors(this IEnumerable actions) /// count how many actions in this list are for changes /// public static int CountChanges(this IEnumerable actions) - => actions.Count(x => x.Change > Core.ChangeType.NoChange); + => actions.Count(x => x.Change > Core.ChangeType.NoChange && x.Change < Core.ChangeType.Hidden); /// /// checks to see if the reuqested action is valid for the configured list of actions. diff --git a/uSync.BackOffice/SyncHandlers/Handlers/DataTypeHandler.cs b/uSync.BackOffice/SyncHandlers/Handlers/DataTypeHandler.cs index b13b461d..e85683d2 100644 --- a/uSync.BackOffice/SyncHandlers/Handlers/DataTypeHandler.cs +++ b/uSync.BackOffice/SyncHandlers/Handlers/DataTypeHandler.cs @@ -26,7 +26,7 @@ namespace uSync.BackOffice.SyncHandlers.Handlers /// Handler to manage DataTypes via uSync /// [SyncHandler(uSyncConstants.Handlers.DataTypeHandler, "Datatypes", "DataTypes", uSyncConstants.Priorites.DataTypes, - Icon = "icon-autofill", EntityType = UdiEntityType.DataType)] + Icon = "icon-autofill", IsTwoPass = true, EntityType = UdiEntityType.DataType)] public class DataTypeHandler : SyncHandlerContainerBase, ISyncHandler, ISyncPostImportHandler, INotificationHandler>, INotificationHandler>, @@ -61,25 +61,39 @@ public DataTypeHandler( /// Datatypes have to exist early on so DocumentTypes can reference them, but /// some doctypes reference content or document types, so we re-process them /// at the end of the import process to ensure those settings can be made too. + /// + /// HOWEVER: The above isn't a problem Umbraco 10+ - the references can be set + /// before the actual doctypes exist, so we can do that in one pass. + /// + /// HOWEVER: If we move deletes to the end , we still need to process them. + /// but deletes are always 'change' = 'Hidden', so we only process hidden changes /// public override IEnumerable ProcessPostImport(string folder, IEnumerable actions, HandlerSettings config) { if (actions == null || !actions.Any()) return null; - foreach (var action in actions) + var results = new List(); + + + // we only do deletes here. + foreach (var action in actions.Where(x => x.Change == ChangeType.Hidden)) { - var result = Import(action.FileName, config, SerializerFlags.None); - foreach (var attempt in result) - { - if (attempt.Success && attempt.Item is IDataType dataType) - { - ImportSecondPass(action.FileName, dataType, config, null); - } - } + var result = Import(action.FileName, config, SerializerFlags.LastPass); + results.AddRange(result); + + //foreach (var attempt in result) + //{ + // if (attempt.Success && attempt.Item is IDataType dataType) + // { + // ImportSecondPass(action.FileName, dataType, config, null); + // } + //} } - return CleanFolders(folder, -1); + results.AddRange(CleanFolders(folder, -1)); + + return results; } /// diff --git a/uSync.BackOffice/SyncHandlers/SyncHandlerContainerBase.cs b/uSync.BackOffice/SyncHandlers/SyncHandlerContainerBase.cs index 799eb14b..c95b2c43 100644 --- a/uSync.BackOffice/SyncHandlers/SyncHandlerContainerBase.cs +++ b/uSync.BackOffice/SyncHandlers/SyncHandlerContainerBase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Xml.Linq; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; @@ -173,5 +174,26 @@ private void ProcessContainerChanges(IEnumerable containers) } } } + + /// + /// Does this item match the one in a given xml file? + /// + /// + /// container based tree's aren't really trees - as in things can't have the same + /// name inside a folder as something else that might be outside the folder. + /// + /// this means when we are comparing files for clean up, we also want to check the + /// alias. so we check the key (in the base) and if doesn't match we check the alias. + /// + /// under default setup none of this matters because the name of the item is the file + /// name so we find/overwrite it anyway, + /// + /// but for guid / folder structured setups we need to do this compare. + /// + protected override bool DoItemsMatch(XElement node, TObject item) + { + if (base.DoItemsMatch(node, item)) return true; + return node.GetAlias().InvariantEquals(GetItemAlias(item)); + } } } diff --git a/uSync.Backoffice.Assets/App_Plugins/uSync/components/usync.reportview.component.js b/uSync.Backoffice.Assets/App_Plugins/uSync/components/usync.reportview.component.js index 6456705a..fd8bcf22 100644 --- a/uSync.Backoffice.Assets/App_Plugins/uSync/components/usync.reportview.component.js +++ b/uSync.Backoffice.Assets/App_Plugins/uSync/components/usync.reportview.component.js @@ -42,7 +42,7 @@ ///////// function showChange(change) { - return vm.showAll || (change !== 'NoChange' && change !== 'Removed'); + return vm.showAll || (change !== 'NoChange' && change !== 'Removed' && change !== 'Hidden'); } function hasFailedDetail(details) { @@ -111,7 +111,7 @@ function countChanges(changes) { var count = 0; angular.forEach(changes, function (val, key) { - if (val.change !== 'NoChange') { + if (val.change !== 'NoChange' && val.change !== 'Hidden') { count++; } }); diff --git a/uSync.Backoffice.Assets/App_Plugins/uSync/settings/usync.controller.js b/uSync.Backoffice.Assets/App_Plugins/uSync/settings/usync.controller.js index 1cb09130..483c715d 100644 --- a/uSync.Backoffice.Assets/App_Plugins/uSync/settings/usync.controller.js +++ b/uSync.Backoffice.Assets/App_Plugins/uSync/settings/usync.controller.js @@ -593,7 +593,7 @@ function countChanges(changes) { var count = 0; angular.forEach(changes, function (val, key) { - if (val.change !== 'NoChange') { + if (val.change !== 'NoChange' && val.change !== 'Hidden') { count++; } }); diff --git a/uSync.Core/ChangeType.cs b/uSync.Core/ChangeType.cs index b4a8f1d2..63e67628 100644 --- a/uSync.Core/ChangeType.cs +++ b/uSync.Core/ChangeType.cs @@ -50,7 +50,15 @@ public enum ChangeType : int Mismatch, [EnumMember(Value = "ParentMissing")] - ParentMissing + ParentMissing, + + /// + /// Hidden changes, don't show up in the UI + /// but they are processed like changes, so + /// get passed to second and last pass attempts + /// + [EnumMember(Value = "Hidden")] + Hidden = 101 } } diff --git a/uSync.Core/Serialization/SerializerFlags.cs b/uSync.Core/Serialization/SerializerFlags.cs index 6a42c867..bf10bb21 100644 --- a/uSync.Core/Serialization/SerializerFlags.cs +++ b/uSync.Core/Serialization/SerializerFlags.cs @@ -7,6 +7,10 @@ public enum SerializerFlags OnePass = 2, // do this in one pass DoNotSave = 4, // don't save FailMissingParent = 8, // fail if the parent item is missing - CreateOnly = 16 // only create, if the item is already there we don't overwrite. + CreateOnly = 16, // only create, if the item is already there we don't overwrite. + FirstPast = 32, // this is the first pass, sometime we might not do stuff on first pass. + SecondPass = 64, // this is the second pass + LastPass = 128, // this is the last pass. + } } diff --git a/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs b/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs index 3dc915ee..f3306582 100644 --- a/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs +++ b/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs @@ -52,6 +52,44 @@ public DataTypeSerializer(IEntityService entityService, ILogger + /// Process deletes + /// + /// + /// datatypes are deleted late (in the last pass) + /// this means they are actually deleted at the very + /// end of the process. + /// + /// In theory this should be fine, + /// + /// any content types that may or may not use + /// datatypes we are about to delete will have + /// already been updated. + /// + /// by moving the datatypes to the end we capture the + /// case where the datatype might have been replaced + /// in the content type, by not deleting first we + /// stop the triggering of any of Umbraco's delete + /// processes. + /// + /// this only works because we are keeping the track of + /// all the deletes and renames when they happen + /// and we can only reliably do that for items + /// that have ContainerTree's because they are not + /// real trees - but flat (each alias is unique) + /// + protected override SyncAttempt ProcessDelete(Guid key, string alias, SerializerFlags flags) + { + if (flags.HasFlag(SerializerFlags.LastPass)) + { + logger.LogDebug("Processing deletes as part of the last pass)"); + return base.ProcessDelete(key, alias, flags); + } + + logger.LogDebug("Delete not processing as this is not the final pass"); + return SyncAttempt.Succeed(alias, ChangeType.Hidden); + } + protected override SyncAttempt DeserializeCore(XElement node, SyncSerializerOptions options) { var info = node.Element(uSyncConstants.Xml.Info);