From 84c75794e96f6901e897eda3ce0cd9a970a6a968 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Wed, 29 Jan 2020 14:48:32 +0000 Subject: [PATCH] Fix #73 - Fix FileClash code, so we only check on level based items (content,media) --- .../SyncHandlers/SyncHandlerBase.cs | 37 ++++--- .../Handlers/ContentHandler.cs | 5 +- .../Handlers/ContentHandlerBase.cs | 82 ++++++++++++++++ .../Handlers/ContentTemplateHandler.cs | 2 +- .../Handlers/MediaHandler.cs | 3 +- .../Serializers/ContentSerializer.cs | 5 +- .../Serializers/ContentSerializerBase.cs | 96 +++++++++++++++---- .../Serializers/ContentTemplateSerializer.cs | 3 +- .../Serializers/ISyncContentSerializer.cs | 14 +++ .../Serializers/MediaSerializer.cs | 4 +- .../uSync8.ContentEdition.csproj | 2 + uSync8.Site/config/uSync8.config | 3 +- 12 files changed, 209 insertions(+), 47 deletions(-) create mode 100644 uSync8.ContentEdition/Handlers/ContentHandlerBase.cs create mode 100644 uSync8.ContentEdition/Serializers/ISyncContentSerializer.cs diff --git a/uSync8.BackOffice/SyncHandlers/SyncHandlerBase.cs b/uSync8.BackOffice/SyncHandlers/SyncHandlerBase.cs index 81116487..6f0d39aa 100644 --- a/uSync8.BackOffice/SyncHandlers/SyncHandlerBase.cs +++ b/uSync8.BackOffice/SyncHandlers/SyncHandlerBase.cs @@ -467,7 +467,7 @@ virtual public IEnumerable Export(TObject item, string folder, Hand } else { - return uSyncAction.SetAction(true, filename, type: typeof(TObject), change: ChangeType.NoChange, message: "Not Exported (Based on config)").AsEnumerableOfOne(); + return uSyncAction.SetAction(true, filename, type: typeof(TObject), change: ChangeType.NoChange, message: "Not Exported (Based on config)", filename: filename).AsEnumerableOfOne(); } } @@ -685,31 +685,28 @@ virtual protected string GetPath(string folder, TObject item, bool GuidNames, bo var path = $"{folder}/{this.GetItemPath(item, GuidNames, isFlat)}.config"; // if this is flat but not using guid filenames, then we check for clashes. - if (isFlat && !GuidNames) return CheckAndFixFileClash(path, GetItemKey(item)); + if (isFlat && !GuidNames) return CheckAndFixFileClash(path, item); return path; } virtual protected Guid GetItemKey(TObject item) => item.Key; - private string CheckAndFixFileClash(string path, Guid key) - { - if (syncFileService.FileExists(path)) - { - var node = syncFileService.LoadXElement(path); - if (node != null && node.GetKey() != key) - { - // clash, we should append something - var append = key.ToShortKeyString(8); // (this is the shortened guid like media folders do) - return Path.Combine(Path.GetDirectoryName(path), - Path.GetFileNameWithoutExtension(path) + "_" + append + Path.GetExtension(path)); - } - - } - return path; - } - - + /// + /// clashes we want to resolve can only occur, when the + /// items can be called the same but in be in diffrent places (e.g content, media). + /// + /// + /// + /// + virtual protected string CheckAndFixFileClash(string path, TObject item) + => path; + /// + /// any class that overrides FixFileClash will need to get a match string + /// from the xml, so its on the base class to avoid duplication + /// + protected virtual string GetXmlMatchString(XElement node) + => $"{node.GetAlias()}_{node.GetLevel()}".ToLower(); virtual public uSyncAction Rename(TObject item) => new uSyncAction(); diff --git a/uSync8.ContentEdition/Handlers/ContentHandler.cs b/uSync8.ContentEdition/Handlers/ContentHandler.cs index 6e4aba70..948a3b7f 100644 --- a/uSync8.ContentEdition/Handlers/ContentHandler.cs +++ b/uSync8.ContentEdition/Handlers/ContentHandler.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Xml.Linq; + using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; @@ -14,7 +15,6 @@ using uSync8.BackOffice.SyncHandlers; using uSync8.Core.Dependency; using uSync8.Core.Extensions; -using uSync8.Core.Models; using uSync8.Core.Serialization; using uSync8.Core.Tracking; @@ -24,7 +24,7 @@ namespace uSync8.ContentEdition.Handlers { [SyncHandler("contentHandler", "Content", "Content", uSyncBackOfficeConstants.Priorites.Content , Icon = "icon-document usync-addon-icon", IsTwoPass = true, EntityType = UdiEntityType.Document)] - public class ContentHandler : SyncHandlerTreeBase, ISyncHandler, ISyncExtendedHandler + public class ContentHandler : ContentHandlerBase, ISyncHandler, ISyncExtendedHandler { public override string Group => uSyncBackOfficeConstants.Groups.Content; @@ -145,6 +145,5 @@ protected override bool ShouldExport(XElement node, HandlerSettings config) return true; } - } } diff --git a/uSync8.ContentEdition/Handlers/ContentHandlerBase.cs b/uSync8.ContentEdition/Handlers/ContentHandlerBase.cs new file mode 100644 index 00000000..80fe91cc --- /dev/null +++ b/uSync8.ContentEdition/Handlers/ContentHandlerBase.cs @@ -0,0 +1,82 @@ +using System.IO; + +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Services; + +using uSync8.BackOffice.Services; +using uSync8.BackOffice.SyncHandlers; +using uSync8.ContentEdition.Serializers; +using uSync8.Core; +using uSync8.Core.Dependency; +using uSync8.Core.Extensions; +using uSync8.Core.Serialization; +using uSync8.Core.Tracking; + +namespace uSync8.ContentEdition.Handlers +{ + /// + /// base for all content based handlers + /// + /// + /// Content based handlers can have the same name in diffrent + /// places around the tree, so we have to check for file name + /// clashes. + /// + public abstract class ContentHandlerBase : SyncHandlerTreeBase + where TObject : IContentBase + where TService : IService + { + protected ContentHandlerBase( + IEntityService entityService, + IProfilingLogger logger, + ISyncSerializer serializer, + ISyncTracker tracker, + SyncFileService syncFileService) + : base(entityService, logger, serializer, tracker, syncFileService) + { } + + protected ContentHandlerBase( + IEntityService entityService, + IProfilingLogger logger, + ISyncSerializer serializer, + ISyncTracker tracker, + ISyncDependencyChecker checker, + SyncFileService syncFileService) + : base(entityService, logger, serializer, tracker, checker, syncFileService) + { } + + + + protected override string CheckAndFixFileClash(string path, TObject item) + { + if (syncFileService.FileExists(path)) + { + var itemKey = item.Key; + var node = syncFileService.LoadXElement(path); + + if (node == null) return path; + if (item.Key == node.GetKey()) return path; + if (GetXmlMatchString(node) == GetItemMatchString(item)) return path; + + // get here we have a clash, we should append something + var append = item.Key.ToShortKeyString(8); // (this is the shortened guid like media folders do) + return Path.Combine(Path.GetDirectoryName(path), + Path.GetFileNameWithoutExtension(path) + "_" + append + Path.GetExtension(path)); + } + + return path; + } + + protected virtual string GetItemMatchString(TObject item) + { + var level = item.Level; + if (item.Trashed && serializer is ISyncContentSerializer contentSerializer) + { + level = contentSerializer.GetLevel(item); + } + return $"{item.Name}_{level}".ToLower(); + } + + } +} diff --git a/uSync8.ContentEdition/Handlers/ContentTemplateHandler.cs b/uSync8.ContentEdition/Handlers/ContentTemplateHandler.cs index b683fb0f..024639e8 100644 --- a/uSync8.ContentEdition/Handlers/ContentTemplateHandler.cs +++ b/uSync8.ContentEdition/Handlers/ContentTemplateHandler.cs @@ -18,7 +18,7 @@ namespace uSync8.ContentEdition.Handlers { [SyncHandler("contentTemplateHandler", "Blueprints", "Blueprints", uSyncBackOfficeConstants.Priorites.ContentTemplate , Icon = "icon-document-dashed-line usync-addon-icon", IsTwoPass = true, EntityType = UdiEntityType.DocumentBlueprint)] - public class ContentTemplateHandler : SyncHandlerTreeBase, ISyncHandler, ISyncExtendedHandler + public class ContentTemplateHandler : ContentHandlerBase, ISyncHandler, ISyncExtendedHandler { public override string Group => uSyncBackOfficeConstants.Groups.Content; diff --git a/uSync8.ContentEdition/Handlers/MediaHandler.cs b/uSync8.ContentEdition/Handlers/MediaHandler.cs index 99a75a90..1424f42c 100644 --- a/uSync8.ContentEdition/Handlers/MediaHandler.cs +++ b/uSync8.ContentEdition/Handlers/MediaHandler.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Xml.Linq; + using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; @@ -23,7 +24,7 @@ namespace uSync8.ContentEdition.Handlers { [SyncHandler("mediaHandler", "Media", "Media", uSyncBackOfficeConstants.Priorites.Media, Icon = "icon-picture usync-addon-icon", IsTwoPass = true, EntityType = UdiEntityType.Media)] - public class MediaHandler : SyncHandlerTreeBase, ISyncHandler, ISyncExtendedHandler + public class MediaHandler : ContentHandlerBase, ISyncHandler, ISyncExtendedHandler { public override string Group => uSyncBackOfficeConstants.Groups.Content; diff --git a/uSync8.ContentEdition/Serializers/ContentSerializer.cs b/uSync8.ContentEdition/Serializers/ContentSerializer.cs index d67aed71..fa4d0b83 100644 --- a/uSync8.ContentEdition/Serializers/ContentSerializer.cs +++ b/uSync8.ContentEdition/Serializers/ContentSerializer.cs @@ -29,15 +29,18 @@ public class ContentSerializer : ContentSerializerBase, ISyncSerialize public ContentSerializer( IEntityService entityService, ILocalizationService localizationService, + IRelationService relationService, ILogger logger, IContentService contentService, IFileService fileService, SyncValueMapperCollection syncMappers) - : base(entityService, localizationService, logger, UmbracoObjectTypes.Document, syncMappers) + : base(entityService, localizationService, relationService, logger, UmbracoObjectTypes.Document, syncMappers) { this.contentService = contentService; this.fileService = fileService; + this.relationAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; + performDoubleLookup = UmbracoVersion.LocalVersion.Major != 8 || UmbracoVersion.LocalVersion.Minor < 4; } diff --git a/uSync8.ContentEdition/Serializers/ContentSerializerBase.cs b/uSync8.ContentEdition/Serializers/ContentSerializerBase.cs index 303302cc..64e3f0f0 100644 --- a/uSync8.ContentEdition/Serializers/ContentSerializerBase.cs +++ b/uSync8.ContentEdition/Serializers/ContentSerializerBase.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Xml.Linq; @@ -11,24 +10,30 @@ using Umbraco.Core.Services; using uSync8.ContentEdition.Mapping; +using uSync8.Core; using uSync8.Core.Extensions; +using uSync8.Core.Models; using uSync8.Core.Serialization; namespace uSync8.ContentEdition.Serializers { - public abstract class ContentSerializerBase : SyncTreeSerializerBase + public abstract class ContentSerializerBase : SyncTreeSerializerBase, ISyncContentSerializer where TObject : IContentBase { protected UmbracoObjectTypes umbracoObjectType; protected SyncValueMapperCollection syncMappers; protected ILocalizationService localizationService; + protected IRelationService relationService; protected Dictionary pathCache; + protected string relationAlias; + public ContentSerializerBase( IEntityService entityService, ILocalizationService localizationService, + IRelationService relationService, ILogger logger, UmbracoObjectTypes umbracoObjectType, SyncValueMapperCollection syncMappers) @@ -38,8 +43,11 @@ public ContentSerializerBase( this.syncMappers = syncMappers; this.localizationService = localizationService; + this.relationService = relationService; this.pathCache = new Dictionary(); + + } protected virtual XElement InitializeNode(TObject item, string typeName) @@ -47,11 +55,39 @@ protected virtual XElement InitializeNode(TObject item, string typeName) var node = new XElement(this.ItemType, new XAttribute("Key", item.Key), new XAttribute("Alias", item.Name), - new XAttribute("Level", item.Level)); + new XAttribute("Level", GetLevel(item))); return node; } + public virtual int GetLevel(TObject item) + { + if (!item.Trashed || string.IsNullOrWhiteSpace(relationAlias)) return item.Level; + + // if the item is trashed,then it's level is going to be wrong. + // we need to go to the relations service, work out who the parent was, + // and get the level of that + 1; + var parent = GetTrashedParent(item); + if (parent != null) + return parent.Level + 1; + + + return item.Level; + } + + private IEntitySlim GetTrashedParent(TObject item) + { + if (!item.Trashed || string.IsNullOrWhiteSpace(relationAlias)) return null; + + var parents = relationService.GetByChild(item, relationAlias); + if (parents != null && parents.Any()) + { + return entityService.Get(parents.FirstOrDefault().ParentId); + } + + return null; + } + protected virtual XElement SerializeInfo(TObject item) { var info = new XElement("Info"); @@ -70,7 +106,7 @@ protected virtual XElement SerializeInfo(TObject item) info.Add(new XElement("Parent", new XAttribute("Key", parentKey), parentName)); info.Add(new XElement("Path", GetItemPath(item))); - info.Add(new XElement("Trashed", item.Trashed)); + info.Add(GetTrashedInfo(item)); info.Add(new XElement("ContentType", item.ContentType.Alias)); info.Add(new XElement("CreateDate", item.CreateDate)); @@ -87,6 +123,20 @@ protected virtual XElement SerializeInfo(TObject item) return info; } + private XElement GetTrashedInfo(TObject item) + { + var trashed = new XElement("Trashed", item.Trashed); + if (item.Trashed) + { + var trashedParent = GetTrashedParent(item); + if (trashedParent != null) + { + trashed.Add(new XAttribute("Parent", trashedParent.Key)); + } + } + return trashed; + } + protected string[] dontSerialize = new string[] { }; protected virtual XElement SerializeProperties(TObject item) @@ -230,25 +280,25 @@ protected Attempt DeserializeProperties(TObject item, XElement node) // if (!current.PropertyType.VariesByCulture()) { - // if we get here, then things are wrong, so we will try to fix them. - // - // if the content config thinks it should vary by culture, but the document type doesn't - // then we can check if this is default language, and use that to se the value - if (!culture.InvariantEquals(localizationService.GetDefaultLanguageIsoCode())) - { - // this culture is not the default for the site, so don't use it to - // set the single language value. - break; - } - logger.Warn($"Cannot set value on culture {culture} because it is not avalible for this property - value in default language will be used"); - culture = string.Empty; + // if we get here, then things are wrong, so we will try to fix them. + // + // if the content config thinks it should vary by culture, but the document type doesn't + // then we can check if this is default language, and use that to se the value + if (!culture.InvariantEquals(localizationService.GetDefaultLanguageIsoCode())) + { + // this culture is not the default for the site, so don't use it to + // set the single language value. + break; + } + logger.Warn($"Cannot set value on culture {culture} because it is not avalible for this property - value in default language will be used"); + culture = string.Empty; } else if (!item.AvailableCultures.InvariantContains(culture)) { // this culture isn't one of the ones, that can be set on this language. logger.Warn($"Culture {culture} is not one of the avalible cultures, so we cannot set this value"); break; - } + } } var itemValue = GetImportValue(propValue, current.PropertyType, culture, segment); item.SetValue(alias, itemValue, culture, segment); @@ -344,7 +394,7 @@ protected virtual string GetItemPath(IEntitySlim item) var path = ""; if (item.ParentId != -1) { - var parent = entityService.Get(item.ParentId); + var parent = entityService.Get(item.ParentId); if (parent != null) path += GetItemPath(parent); } @@ -353,6 +403,16 @@ protected virtual string GetItemPath(IEntitySlim item) return pathCache[item.Path]; } + public override SyncAttempt SerializeEmpty(TObject item, SyncActionType change, string alias) + { + var attempt = base.SerializeEmpty(item, change, alias); + if (attempt.Success) + { + attempt.Item.Add(new XAttribute("Level", GetLevel(item))); + } + return attempt; + } + #region Finders // Finders - used on importing, getting things that are already there (or maybe not) diff --git a/uSync8.ContentEdition/Serializers/ContentTemplateSerializer.cs b/uSync8.ContentEdition/Serializers/ContentTemplateSerializer.cs index 32c163ff..a5973c66 100644 --- a/uSync8.ContentEdition/Serializers/ContentTemplateSerializer.cs +++ b/uSync8.ContentEdition/Serializers/ContentTemplateSerializer.cs @@ -24,12 +24,13 @@ public class ContentTemplateSerializer : ContentSerializer, ISyncSerializer + { + int GetLevel(TObject item); + } +} diff --git a/uSync8.ContentEdition/Serializers/MediaSerializer.cs b/uSync8.ContentEdition/Serializers/MediaSerializer.cs index 2dc0aa48..11b4d7e0 100644 --- a/uSync8.ContentEdition/Serializers/MediaSerializer.cs +++ b/uSync8.ContentEdition/Serializers/MediaSerializer.cs @@ -35,12 +35,14 @@ public class MediaSerializer : ContentSerializerBase, ISyncSerializer + @@ -273,6 +274,7 @@ + diff --git a/uSync8.Site/config/uSync8.config b/uSync8.Site/config/uSync8.config index dc12e1c5..185380f6 100644 --- a/uSync8.Site/config/uSync8.config +++ b/uSync8.Site/config/uSync8.config @@ -23,9 +23,10 @@ +