diff --git a/uSync.Core/Serialization/Serializers/ContentSerializer.cs b/uSync.Core/Serialization/Serializers/ContentSerializer.cs index bce936557..fdb1fe025 100644 --- a/uSync.Core/Serialization/Serializers/ContentSerializer.cs +++ b/uSync.Core/Serialization/Serializers/ContentSerializer.cs @@ -291,20 +291,9 @@ public int DeserializeWriterInfo(IContent item, XElement node, SyncSerializerOpt public override async Task> DeserializeSecondPassAsync(IContent item, XElement node, SyncSerializerOptions options) { var details = new List(); - // move trashed state to second pass, as the item needs an Id for the relation to work. - details.AddNotNull(await DeserializeTrashed(node, item, Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias)); - - // move sort to second pass, as if we attempt to set this - // on a brand new item, it doesn't get set. - // doing it on second pass ensures it gets set on the item - // after it has been saved by umbraco. - if (!options.GetSetting( - uSyncConstants.DefaultSettings.IgnoreSortOrder, - uSyncConstants.DefaultSettings.IgnoreSortOrder_Default)) - { - var sortOrder = node.Element(uSyncConstants.Xml.Info)?.Element(uSyncConstants.Xml.SortOrder).ValueOrDefault(-1) ?? -1; - details.AddNotNull(HandleSortOrder(item, sortOrder)); - } + + details.AddRange(await this.DeserializeSecondPassSharedAsync(item, node, options, + Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias)); var changes = await DeserializeSchedulesAsync(item, node, options); if (changes.Count != 0) @@ -444,7 +433,7 @@ protected virtual async Task DoSaveOrPublishAsync(ICont logger.LogDebug("Performing Save: {id} {name}", item.Id, item.Name); var result = contentService.Save(item, options.UserId, scheduleCollection); if (result.Success) - item = contentService.GetById(item.Id)!; + item = contentService.GetById(item.Id) ?? item; else { // something went wrong saving. ??? diff --git a/uSync.Core/Serialization/Serializers/ContentSerializerBase.cs b/uSync.Core/Serialization/Serializers/ContentSerializerBase.cs index 8a7545e15..248e2966f 100644 --- a/uSync.Core/Serialization/Serializers/ContentSerializerBase.cs +++ b/uSync.Core/Serialization/Serializers/ContentSerializerBase.cs @@ -606,6 +606,33 @@ protected async Task, string>> DeserializePropertiesAs return Attempt.SucceedWithStatus(errors, changes); } + /// + /// Things most 'IContentBase' serializers need (ones that have trash anyway). + /// + protected async Task> DeserializeSecondPassSharedAsync(TObject item, XElement node, SyncSerializerOptions options, + string trashedRelationAlias) + { + var details = new List(); + + // move trashed state to second pass, as the item needs an Id for the relation to work. + details.AddNotNull(await DeserializeTrashed(node, item, trashedRelationAlias)); + + // move sort to second pass, as if we attempt to set this + // on a brand new item, it doesn't get set. + // doing it on second pass ensures it gets set on the item + // after it has been saved by umbraco. + if (!options.GetSetting( + uSyncConstants.DefaultSettings.IgnoreSortOrder, + uSyncConstants.DefaultSettings.IgnoreSortOrder_Default)) + { + var sortOrder = node.Element(uSyncConstants.Xml.Info)?.Element(uSyncConstants.Xml.SortOrder).ValueOrDefault(-1) ?? -1; + details.AddNotNull(HandleSortOrder(item, sortOrder)); + } + + return details; + } + + /// /// compares to object values to see if they are the same. /// diff --git a/uSync.Core/Serialization/Serializers/ContentTypeBaseSerializer.cs b/uSync.Core/Serialization/Serializers/ContentTypeBaseSerializer.cs index 3074b6b30..fc123b1af 100644 --- a/uSync.Core/Serialization/Serializers/ContentTypeBaseSerializer.cs +++ b/uSync.Core/Serialization/Serializers/ContentTypeBaseSerializer.cs @@ -185,14 +185,19 @@ protected async Task SerializeStructureAsync(TObject item) var allowedTypeOrdered = item.AllowedContentTypes?.OrderBy(x => x.SortOrder); if (allowedTypeOrdered is null) return node; + int defaultSortOrder = 0; foreach (var allowedType in allowedTypeOrdered) { var allowedItem = await FindItemAsync(allowedType.Key); if (allowedItem != null) { + var sortOrder = allowedType.SortOrder > 0 ? allowedType.SortOrder : defaultSortOrder; + node.Add(new XElement(ItemType, new XAttribute(uSyncConstants.Xml.Key, allowedItem.Key), - new XAttribute(uSyncConstants.Xml.SortOrder, allowedType.SortOrder), allowedItem.Alias)); + new XAttribute(uSyncConstants.Xml.SortOrder, sortOrder), allowedItem.Alias)); + + defaultSortOrder = sortOrder + 1; } } return node; @@ -346,7 +351,11 @@ protected async Task> DeserializeStructureAsync(TObject int sortOrder = 0; - foreach (var baseNode in structure.Elements(ItemType)) + // do it in the sort order (if there is one). + // makes it more likely to 'work' first time. + var nodes = structure.Elements(ItemType).OrderBy(x => x.Attribute(uSyncConstants.Xml.SortOrder).ValueOrDefault(int.MinValue)); + + foreach (var baseNode in nodes) { logger.LogDebug("baseNode {base}", baseNode.ToString()); var alias = baseNode.Value; @@ -375,34 +384,30 @@ protected async Task> DeserializeStructureAsync(TObject if (baseItem != null) { - logger.LogDebug("Structure Found {alias}", baseItem.Alias); allowed.Add(new ContentTypeSort(baseItem.Key, itemSortOrder, baseItem.Alias)); sortOrder = itemSortOrder + 1; } } - logger.LogDebug("Structure: {count} items", allowed.Count); - - if (item.AllowedContentTypes is not null) { // compare the two lists (the equality compare fails because the id value is lazy) - var currentHash = string.Join(":", item.AllowedContentTypes.Select(x => $"{x.Key}-{x.SortOrder}").OrderBy(x => x)); - var newHash = string.Join(":", allowed.Select(x => $"{x.Key}-{x.SortOrder}").OrderBy(x => x)); + var currentHash = string.Join(":", item.AllowedContentTypes.Select(x => $"{x.Key}-{x.SortOrder}").OrderBy(x => x)).GetDeterministicHashCode(); + var newHash = string.Join(":", allowed.Select(x => $"{x.Key}-{x.SortOrder}").OrderBy(x => x)).GetDeterministicHashCode(); - if (!currentHash.Equals(newHash)) + if (currentHash.Equals(newHash) is false) { changes.AddUpdate("Allowed", - string.Join(",", item.AllowedContentTypes.Select(x => x.Alias) ?? []), - string.Join(",", allowed.Select(x => x.Alias) ?? []), "/Structure"); + string.Join("::", item.AllowedContentTypes.Select(x => x.Alias)?? []), + string.Join("::", allowed.Select(x => x.Alias) ?? []), "/Structure"); - logger.LogDebug("Updating allowed content types {old}, {new}", currentHash, newHash); - item.AllowedContentTypes = allowed; + item.AllowedContentTypes = allowed.OrderBy(x => x.SortOrder); } } else { - item.AllowedContentTypes = allowed; + changes.AddNew("Allowed", string.Join("::", allowed.Select(x => x.Alias) ?? []), "/Structure"); + item.AllowedContentTypes = allowed.OrderBy(x => x.SortOrder); } return changes; @@ -1348,7 +1353,12 @@ protected virtual bool PropertyExistsOnComposite(TObject item, string alias, Lis public override async Task SaveItemAsync(TObject item) { - if (item.IsDirty() is false) return; + logger.LogDebug("Save Item {name} ({alias})", item.Name, item.Alias); + if (item.IsDirty() is false) + { + logger.LogDebug("Item not dirty, skipping save"); + return; + } if (item.Id <= 0) { diff --git a/uSync.Core/Serialization/Serializers/MediaSerializer.cs b/uSync.Core/Serialization/Serializers/MediaSerializer.cs index 14937da7d..d6f56dcd1 100644 --- a/uSync.Core/Serialization/Serializers/MediaSerializer.cs +++ b/uSync.Core/Serialization/Serializers/MediaSerializer.cs @@ -99,8 +99,8 @@ protected override async Task> DeserializeCoreAsync(XElement public override async Task> DeserializeSecondPassAsync(IMedia item, XElement node, SyncSerializerOptions options) { - var details = new List(); - details.AddNotNull(await DeserializeTrashed(node, item, Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias)); + var details = await DeserializeSecondPassSharedAsync(item, node, options, + Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias); return SyncAttempt.Succeed(item.Name ?? item.Id.ToString(), item, details.Count == 0 ? ChangeType.NoChange : ChangeType.Import, details);