protected override Item LoadItem(Item item, LoadOptions options) { Assert.ArgumentNotNull(item, "item"); var itemData = new ItemData(item); var configuration = _helper.GetConfigurationForItem(itemData); if (configuration == null) return base.LoadItem(item, options); var sourceStore = configuration.Resolve<ISourceDataStore>(); var targetStore = configuration.Resolve<ITargetDataStore>(); var targetItem = targetStore.GetByPathAndId(itemData.Path, itemData.Id, itemData.DatabaseName); if (targetItem == null) { Log.Warn("Unicorn: Unable to load item because it was not serialized.", this); return base.LoadItem(item, options); } sourceStore.Save(targetItem); return Database.GetItem(item.Uri); }
protected override Item LoadItem(Item item, LoadOptions options) { Assert.ArgumentNotNull(item, "item"); var itemData = new ItemData(item); var configuration = _helper.GetConfigurationForItem(itemData); if (configuration == null) { return(base.LoadItem(item, options)); } var sourceStore = configuration.Resolve <ISourceDataStore>(); var targetStore = configuration.Resolve <ITargetDataStore>(); var targetItem = targetStore.GetByPathAndId(itemData.Path, itemData.Id, itemData.DatabaseName); if (targetItem == null) { Log.Warn("Unicorn: Unable to load item because it was not serialized.", this); return(base.LoadItem(item, options)); } sourceStore.Save(targetItem); return(Database.GetItem(item.Uri)); }
protected override Item LoadItem(Item item, LoadOptions options) { Assert.ArgumentNotNull(item, "item"); var itemData = new ItemData(item); var configuration = _helper.GetConfigurationsForItem(itemData).FirstOrDefault(); // if multiple configs contain item, load from first one if (configuration == null) { return(base.LoadItem(item, options)); } var sourceStore = configuration.Resolve <ISourceDataStore>(); var targetStore = configuration.Resolve <ITargetDataStore>(); var targetItem = targetStore.GetByPathAndId(itemData.Path, itemData.Id, itemData.DatabaseName); if (targetItem == null) { Log.Warn("Unicorn: Unable to load item because it was not serialized.", this); return(base.LoadItem(item, options)); } var result = configuration.Resolve <IPredicate>().Includes(targetItem); sourceStore.Save(targetItem, result.FieldValueManipulator); return(Database.GetItem(item.Uri)); }
public virtual void SaveItem(ItemDefinition itemDefinition, ItemChanges changes, CallContext context) { if (DisableSerialization) { return; } Assert.ArgumentNotNull(itemDefinition, "itemDefinition"); Assert.ArgumentNotNull(changes, "changes"); // get the item we're saving to evaluate with the predicate // NOTE: the item in this state may be incomplete as Sitecore can sometimes send partial item data and rely on changes to do the save // e.g. during package installations. So we have to merge the changes with any existing item data if we save it later, to keep it consistent. IItemData sourceItem = new ItemData(changes.Item); if (!_predicate.Includes(sourceItem).IsIncluded) { return; } string oldName = changes.Renamed ? changes.Properties["name"].OriginalValue.ToString() : string.Empty; if (changes.Renamed && !oldName.Equals(sourceItem.Name, StringComparison.Ordinal)) // it's a rename, in which the name actually changed (template builder will cause 'renames' for the same name!!!) { using (new DatabaseCacheDisabler()) { // disabling the DB caches while running this ensures that any children of the renamed item are retrieved with their proper post-rename paths and thus are not saved at their old location // this allows us to filter out any excluded children by predicate when the data store moves children var predicatedItem = new PredicateFilteredItemData(sourceItem, _predicate); _targetDataStore.MoveOrRenameItem(predicatedItem, changes.Item.Paths.ParentPath + "/" + oldName); } _logger.RenamedItem(_targetDataStore.FriendlyName, sourceItem, oldName); } else if (HasConsequentialChanges(changes)) // it's a simple update - but we reject it if only inconsequential fields (last updated, revision) were changed - again, template builder FTW { var existingSerializedItem = _targetDataStore.GetByPathAndId(sourceItem.Path, sourceItem.Id, sourceItem.DatabaseName); // generated an IItemData from the item changes we received, and apply those changes to the existing serialized item if any if (existingSerializedItem != null) { sourceItem = new ItemChangeApplyingItemData(existingSerializedItem, changes); } else { sourceItem = new ItemChangeApplyingItemData(changes); } _targetDataStore.Save(sourceItem); AddBlobsToCache(sourceItem); _logger.SavedItem(_targetDataStore.FriendlyName, sourceItem, "Saved"); } }
public void SaveItem(ItemDefinition itemDefinition, ItemChanges changes, CallContext context) { if (DisableSerialization) { return; } Assert.ArgumentNotNull(itemDefinition, "itemDefinition"); Assert.ArgumentNotNull(changes, "changes"); // get the item from the database (note: we don't allow TpSync to be a database here, because we handle that below) var sourceItem = GetSourceFromId(changes.Item.ID, allowTpSyncFallback: false); if (sourceItem == null) { if (DisableTransparentSync) { return; } // if TpSync is enabled, we wrap the item changes item directly; the TpSync item will NOT have the new changes as we need to write those here sourceItem = new ItemData(changes.Item); } if (!_predicate.Includes(sourceItem).IsIncluded) { return; } string oldName = changes.Renamed ? changes.Properties["name"].OriginalValue.ToString() : string.Empty; if (changes.Renamed && !oldName.Equals(sourceItem.Name, StringComparison.Ordinal)) // it's a rename, in which the name actually changed (template builder will cause 'renames' for the same name!!!) { using (new DatabaseCacheDisabler()) { // disabling the DB caches while running this ensures that any children of the renamed item are retrieved with their proper post-rename paths and thus are not saved at their old location // this allows us to filter out any excluded children by predicate when the data store moves children var predicatedItem = new PredicateFilteredItemData(sourceItem, _predicate); _targetDataStore.MoveOrRenameItem(predicatedItem, changes.Item.Paths.ParentPath + "/" + oldName); } _logger.RenamedItem(_targetDataStore.FriendlyName, sourceItem, oldName); } else if (HasConsequentialChanges(changes)) // it's a simple update - but we reject it if only inconsequential fields (last updated, revision) were changed - again, template builder FTW { _targetDataStore.Save(sourceItem); AddBlobsToCache(sourceItem); _logger.SavedItem(_targetDataStore.FriendlyName, sourceItem, "Saved"); } }
protected override void ProcessRecord() { var touchedConfigs = new List <IConfiguration>(); IItemData itemData = new ItemData(Item); var configuration = _helper.GetConfigurationsForItem(itemData).FirstOrDefault(); // if multiple configs contain item, load from first one if (configuration == null) { throw new InvalidOperationException($"{itemData.GetDisplayIdentifier()} was not part of any Unicorn configurations."); } touchedConfigs.Add(configuration); var logger = new WebConsoleLogger(new PowershellProgressStatus(Host, "Partial Sync Unicorn"), LogLevel); var helper = configuration.Resolve <SerializationHelper>(); var targetDataStore = configuration.Resolve <ITargetDataStore>(); itemData = targetDataStore.GetByPathAndId(itemData.Path, itemData.Id, itemData.DatabaseName); if (itemData == null) { throw new InvalidOperationException($"Could not do partial sync of {Item.Database.Name}:{Item.Paths.FullPath} because it was not serialized. You may need to perform initial serialization."); } try { logger.Info( $"Processing partial Unicorn configuration {itemData.GetDisplayIdentifier()} (Config: {configuration.Name})"); using (new LoggingContext(logger, configuration)) { if (Recurse.IsPresent) { helper.SyncTree(configuration, partialSyncRoot: itemData); } else { var sourceStore = configuration.Resolve <ISourceDataStore>(); var result = configuration.Resolve <IPredicate>().Includes(itemData); sourceStore.Save(itemData, result.FieldValueManipulator); } } } catch (Exception ex) { logger.Error(ex); throw; } CorePipeline.Run("unicornSyncEnd", new UnicornSyncEndPipelineArgs(new SitecoreLogger(), true, touchedConfigs.ToArray())); }
public void CopyItem(ItemDefinition source, ItemDefinition destination, string copyName, ID copyId, CallContext context) { if (DisableSerialization) { return; } // copying is easy - all we have to do is serialize the copyID. Copied children will all result in multiple calls to CopyItem so we don't even need to worry about them. var copiedItem = new ItemData(Database.GetItem(copyId), _sourceDataStore); if (!_predicate.Includes(copiedItem).IsIncluded) { return; // destination parent is not in a path that we are serializing, so skip out } _targetDataStore.Save(copiedItem); _logger.CopiedItem(_targetDataStore.FriendlyName, () => GetSourceFromId(source.ID), copiedItem); }
protected override Item LoadItem(Item item, LoadOptions options) { Assert.ArgumentNotNull(item, "item"); IItemData itemData = new ItemData(item); var configuration = _helper.GetConfigurationForItem(itemData); if (configuration == null) { return(base.LoadItem(item, options)); } var logger = configuration.Resolve <ILogger>(); var helper = configuration.Resolve <SerializationHelper>(); var targetDataStore = configuration.Resolve <ITargetDataStore>(); itemData = targetDataStore.GetByPathAndId(itemData.Path, itemData.Id, itemData.DatabaseName); if (itemData == null) { logger.Warn("Command sync: Could not do partial sync of " + item.Paths.FullPath + " because the root was not serialized."); return(item); } try { logger.Info("Command Sync: Processing partial Unicorn configuration " + configuration.Name + " under " + itemData.Path); helper.SyncTree(configuration, roots: new[] { itemData }); logger.Info("Command Sync: Completed syncing partial Unicorn configuration " + configuration.Name + " under " + itemData.Path); } catch (Exception ex) { logger.Error(ex); throw; } CorePipeline.Run("unicornSyncEnd", new UnicornSyncEndPipelineArgs(configuration)); return(Database.GetItem(item.Uri)); }
protected override void ProcessRecord() { IItemData itemData = new ItemData(Item); if (Recurse.IsPresent) { if (!_helper.ReserializeTree(itemData)) { throw new InvalidOperationException($"{itemData.GetDisplayIdentifier()} was not part of any Unicorn configuration."); } } else { if (!_helper.ReserializeItem(itemData)) { throw new InvalidOperationException($"{itemData.GetDisplayIdentifier()} was not part of any Unicorn configuration."); } } }
protected override Item LoadItem(Item item, LoadOptions options) { Assert.ArgumentNotNull(item, "item"); IItemData itemData = new ItemData(item); var configuration = _helper.GetConfigurationsForItem(itemData).FirstOrDefault(); // if multiple configs contain item, load from first one if (configuration == null) return base.LoadItem(item, options); var logger = configuration.Resolve<ILogger>(); var helper = configuration.Resolve<SerializationHelper>(); var targetDataStore = configuration.Resolve<ITargetDataStore>(); itemData = targetDataStore.GetByPathAndId(itemData.Path, itemData.Id, itemData.DatabaseName); if (itemData == null) { logger.Warn("Command sync: Could not do partial sync of " + item.Paths.FullPath + " because the root was not serialized."); return item; } try { logger.Info("Command Sync: Processing partial Unicorn configuration " + configuration.Name + " under " + itemData.Path); helper.SyncTree(configuration, roots: new[] { itemData }); logger.Info("Command Sync: Completed syncing partial Unicorn configuration " + configuration.Name + " under " + itemData.Path); } catch (Exception ex) { logger.Error(ex); throw; } CorePipeline.Run("unicornSyncEnd", new UnicornSyncEndPipelineArgs(new StringProgressStatus(), configuration)); return Database.GetItem(item.Uri); }
/// <summary> /// Restoring items from the recycle bin does not invoke the data provider at all, so we have to attach to its event /// to cause restored items to be rewritten to disk if they are included. /// </summary> protected virtual void HandleItemRestored(RestoreItemCompletedEvent restoreItemCompletedEvent) { if (!restoreItemCompletedEvent.DatabaseName.Equals(Database.Name, StringComparison.Ordinal)) { return; } // we use a timer to delay the execution of our handler for a couple seconds. // at the time the handler is called, calling Database.GetItem(id) returns NULL, // or without cache an item with an orphan path. The delay allows Sitecore to catch up // with it. new Timer(state => { var item = GetItemFromId(new ID(restoreItemCompletedEvent.ItemId), true); Assert.IsNotNull(item, "Item that was restored was null."); var iitem = new ItemData(item); SerializeItemIfIncluded(iitem, "Restored"); }, null, 2000, Timeout.Infinite); }
protected override string GetDialogText(SaveArgs args) { var results = new Dictionary<Item, IList<string>>(); foreach (var item in args.Items) { // we grab the existing item from the database. This will NOT include the changed values we're saving. // this is because we want to verify that the base state of the item matches serialized, NOT the state we're saving. // if the base state and the serialized state match we can be pretty sure that the changes we are writing won't clobber anything serialized but not synced Item existingItem = Client.ContentDatabase.GetItem(item.ID, item.Language, item.Version); Assert.IsNotNull(existingItem, "Existing item {0} did not exist! This should never occur.", item.ID); var existingSitecoreItem = new ItemData(existingItem); foreach (var configuration in _configurations) { // ignore conflict checks if Transparent Sync is turned on (in which case this is a tautology - 'get from database' would be 'get from disk' so it always matches if (configuration.Resolve<IUnicornDataProviderConfiguration>().EnableTransparentSync) continue; // ignore conflicts on items that Unicorn is not managing if (!configuration.Resolve<IPredicate>().Includes(existingSitecoreItem).IsIncluded) continue; // evaluator signals that it does not care about conflicts (e.g. NIO) if (!configuration.Resolve<IEvaluator>().ShouldPerformConflictCheck(existingItem)) continue; IItemData serializedItemData = configuration.Resolve<ITargetDataStore>().GetByPathAndId(existingSitecoreItem.Path, existingSitecoreItem.Id, existingSitecoreItem.DatabaseName); // not having an existing serialized version means no possibility of conflict here if (serializedItemData == null) continue; var fieldFilter = configuration.Resolve<IFieldFilter>(); var itemComparer = configuration.Resolve<IItemComparer>(); var fieldIssues = GetFieldSyncStatus(existingSitecoreItem, serializedItemData, fieldFilter, itemComparer); if (fieldIssues.Count == 0) continue; results[existingItem] = fieldIssues; } } // no problems if (results.Count == 0) return null; var sb = new StringBuilder(); if (About.Version.StartsWith("7") || About.Version.StartsWith("6")) { // older Sitecores used \n to format dialog text sb.Append("CRITICAL MESSAGE FROM UNICORN:\n"); sb.Append("You need to run a Unicorn sync. The following fields did not match the serialized version:\n"); foreach (var item in results) { if (results.Count > 1) sb.AppendFormat("\n{0}: {1}", item.Key.DisplayName, string.Join(", ", item.Value)); else sb.AppendFormat("\n{0}", string.Join(", ", item.Value)); } sb.Append("\n\nDo you want to overwrite anyway?\nTHIS MAY CAUSE LOST WORK."); } else { // Sitecore 8.x+ uses HTML to format dialog text sb.Append("<p style=\"font-weight: bold; margin-bottom: 1em;\">CRITICAL MESSAGE FROM UNICORN:</p>"); sb.Append("<p>You need to run a Unicorn sync. The following fields did not match the serialized version:</p><ul style=\"margin: 1em 0\">"); foreach (var item in results) { if (results.Count > 1) sb.AppendFormat("<li>{0}: {1}</li>", item.Key.DisplayName, string.Join(", ", item.Value)); else sb.AppendFormat("<li>{0}</li>", string.Join(", ", item.Value)); } sb.Append("</ul><p>Do you want to overwrite anyway?<br>THIS MAY CAUSE LOST WORK.</p>"); } return sb.ToString(); }
public void CopyItem(ItemDefinition source, ItemDefinition destination, string copyName, ID copyId, CallContext context) { if (DisableSerialization) return; // copying is easy - all we have to do is serialize the copyID. Copied children will all result in multiple calls to CopyItem so we don't even need to worry about them. var copiedItem = new ItemData(Database.GetItem(copyId), _sourceDataStore); if (!_predicate.Includes(copiedItem).IsIncluded) return; // destination parent is not in a path that we are serializing, so skip out _targetDataStore.Save(copiedItem); _logger.CopiedItem(_targetDataStore.FriendlyName, () => GetSourceFromId(source.ID), copiedItem); }
public void SaveItem(ItemDefinition itemDefinition, ItemChanges changes, CallContext context) { if (DisableSerialization) return; Assert.ArgumentNotNull(itemDefinition, "itemDefinition"); Assert.ArgumentNotNull(changes, "changes"); // get the item from the database (note: we don't allow TpSync to be a database here, because we handle that below) var sourceItem = GetSourceFromId(changes.Item.ID, allowTpSyncFallback: false); if (sourceItem == null) { if (DisableTransparentSync) return; // if TpSync is enabled, we wrap the item changes item directly; the TpSync item will NOT have the new changes as we need to write those here sourceItem = new ItemData(changes.Item); } if (!_predicate.Includes(sourceItem).IsIncluded) return; string oldName = changes.Renamed ? changes.Properties["name"].OriginalValue.ToString() : string.Empty; if (changes.Renamed && !oldName.Equals(sourceItem.Name, StringComparison.Ordinal)) // it's a rename, in which the name actually changed (template builder will cause 'renames' for the same name!!!) { using (new DatabaseCacheDisabler()) { // disabling the DB caches while running this ensures that any children of the renamed item are retrieved with their proper post-rename paths and thus are not saved at their old location // this allows us to filter out any excluded children by predicate when the data store moves children var predicatedItem = new PredicateFilteredItemData(sourceItem, _predicate); _targetDataStore.MoveOrRenameItem(predicatedItem, changes.Item.Paths.ParentPath + "/" + oldName); } _logger.RenamedItem(_targetDataStore.FriendlyName, sourceItem, oldName); } else if (HasConsequentialChanges(changes)) // it's a simple update - but we reject it if only inconsequential fields (last updated, revision) were changed - again, template builder FTW { _targetDataStore.Save(sourceItem); AddBlobsToCache(sourceItem); _logger.SavedItem(_targetDataStore.FriendlyName, sourceItem, "Saved"); } }
/// <summary> /// Restoring items from the recycle bin does not invoke the data provider at all, so we have to attach to its event /// to cause restored items to be rewritten to disk if they are included. /// </summary> protected virtual void HandleItemRestored(RestoreItemCompletedEvent restoreItemCompletedEvent) { if (!restoreItemCompletedEvent.DatabaseName.Equals(Database.Name, StringComparison.Ordinal)) return; // we use a timer to delay the execution of our handler for a couple seconds. // at the time the handler is called, calling Database.GetItem(id) returns NULL, // or without cache an item with an orphan path. The delay allows Sitecore to catch up // with it. new Timer(state => { var item = GetItemFromId(new ID(restoreItemCompletedEvent.ItemId), true); Assert.IsNotNull(item, "Item that was restored was null."); var iitem = new ItemData(item); SerializeItemIfIncluded(iitem, "Restored"); }, null, 2000, Timeout.Infinite); }
protected override string GetDialogText(SaveArgs args) { var results = new Dictionary<Item, IList<string>>(); try { foreach (var item in args.Items) { Item existingItem = Client.ContentDatabase.GetItem(item.ID, item.Language, item.Version); Assert.IsNotNull(existingItem, "Existing item {0} did not exist! This should never occur.", item.ID); var existingSitecoreItem = new ItemData(existingItem); foreach (var configuration in _configurations) { // ignore conflicts on items that Unicorn is not managing if (!configuration.Resolve<IPredicate>().Includes(existingSitecoreItem).IsIncluded) continue; IItemData serializedItemData = configuration.Resolve<ITargetDataStore>().GetByPathAndId(existingSitecoreItem.Path, existingSitecoreItem.Id, existingSitecoreItem.DatabaseName); // not having an existing serialized version means no possibility of conflict here if (serializedItemData == null) continue; var fieldFilter = configuration.Resolve<IFieldFilter>(); var itemComparer = configuration.Resolve<IItemComparer>(); var fieldIssues = GetFieldSyncStatus(existingSitecoreItem, serializedItemData, fieldFilter, itemComparer); if (fieldIssues.Count == 0) continue; results.Add(existingItem, fieldIssues); } } // no problems if (results.Count == 0) return null; var sb = new StringBuilder(); sb.Append("CRITICAL MESSAGE FROM UNICORN:\n"); sb.Append("You need to run a Unicorn sync. The following fields did not match the serialized version:\n"); foreach (var item in results) { if(results.Count > 1) sb.AppendFormat("\n{0}: {1}", item.Key.DisplayName, string.Join(", ", item.Value)); else sb.AppendFormat("\n{0}", string.Join(", ", item.Value)); } sb.Append("\n\nDo you want to overwrite anyway?\nTHIS MAY CAUSE LOST WORK."); return sb.ToString(); } catch (Exception ex) { Log.Error("Exception occurred while performing serialization conflict check!", ex, this); return "Exception occurred: " + ex.Message; // this will cause a few retries } }
public virtual void SaveItem(ItemDefinition itemDefinition, ItemChanges changes, CallContext context) { if (DisableSerialization) { return; } Assert.ArgumentNotNull(itemDefinition, "itemDefinition"); Assert.ArgumentNotNull(changes, "changes"); // get the item we're saving to evaluate with the predicate // NOTE: the item in this state may be incomplete as Sitecore can sometimes send partial item data and rely on changes to do the save // e.g. during package installations. So we have to merge the changes with any existing item data if we save it later, to keep it consistent. IItemData sourceItem = new ItemData(changes.Item); if (!_predicate.Includes(sourceItem).IsIncluded) { return; } // reject if only inconsequential fields (e.g. last updated, revision) were changed - again, template builder FTW with the junk saves if (!HasConsequentialChanges(changes)) { return; } string existingItemPath = sourceItem.Path; // check if the save includes a rename as part of the operation, in which case we have to get the existing item, if any, from the OLD path pre-rename // note that if an item is renamed to the same name this will simply fall through as not a rename if (changes.Renamed) { string oldName = changes.Properties["name"].OriginalValue.ToString(); existingItemPath = changes.Item.Paths.ParentPath + "/" + oldName; } // we find the existing serialized item, with which we want to merge the item changes, if it exists. If not then the changes are the source of all truth. var existingSerializedItem = _targetDataStore.GetByPathAndId(existingItemPath, sourceItem.Id, sourceItem.DatabaseName); // generate an IItemData from the item changes we received, and apply those changes to the existing serialized item if any var changesAppliedItem = existingSerializedItem != null ? new ItemChangeApplyingItemData(existingSerializedItem, changes) : new ItemChangeApplyingItemData(changes); // put any media blob IDs on this item into the media blob cache (used for TpSync media - does not cache the blob just the filename it lives in) AddBlobsToCache(changesAppliedItem); // check for renamed item (existing path != source path -> rename) if (!existingItemPath.Equals(sourceItem.Path, StringComparison.Ordinal)) { // this allows us to filter out any excluded children when the data store moves children to the renamed path var predicatedItem = new PredicateFilteredItemData(changesAppliedItem, _predicate); // change the item's name before sending it to the data store (note: the data store will normalize any child paths for us) var alteredPathItem = new RenamedItemData(predicatedItem, sourceItem.Name); _targetDataStore.MoveOrRenameItem(alteredPathItem, existingItemPath); _logger.RenamedItem(_targetDataStore.FriendlyName, alteredPathItem, existingItemPath.Substring(existingItemPath.LastIndexOf('/') + 1)); return; } // if we get here, it's just a save, not a rename _targetDataStore.Save(changesAppliedItem); _logger.SavedItem(_targetDataStore.FriendlyName, changesAppliedItem, "Saved"); }
protected override string GetDialogText(SaveArgs args) { var results = new Dictionary <Item, IList <string> >(); try { foreach (var item in args.Items) { // we grab the existing item from the database. This will NOT include the changed values we're saving. // this is because we want to verify that the base state of the item matches serialized, NOT the state we're saving. // if the base state and the serialized state match we can be pretty sure that the changes we are writing won't clobber anything serialized but not synced Item existingItem = Client.ContentDatabase.GetItem(item.ID, item.Language, item.Version); Assert.IsNotNull(existingItem, "Existing item {0} did not exist! This should never occur.", item.ID); var existingSitecoreItem = new ItemData(existingItem); foreach (var configuration in _configurations) { // ignore conflicts on items that Unicorn is not managing if (!configuration.Resolve <IPredicate>().Includes(existingSitecoreItem).IsIncluded) { continue; } IItemData serializedItemData = configuration.Resolve <ITargetDataStore>().GetByPathAndId(existingSitecoreItem.Path, existingSitecoreItem.Id, existingSitecoreItem.DatabaseName); // not having an existing serialized version means no possibility of conflict here if (serializedItemData == null) { continue; } var fieldFilter = configuration.Resolve <IFieldFilter>(); var itemComparer = configuration.Resolve <IItemComparer>(); var fieldIssues = GetFieldSyncStatus(existingSitecoreItem, serializedItemData, fieldFilter, itemComparer); if (fieldIssues.Count == 0) { continue; } results.Add(existingItem, fieldIssues); } } // no problems if (results.Count == 0) { return(null); } var sb = new StringBuilder(); sb.Append("CRITICAL MESSAGE FROM UNICORN:\n"); sb.Append("You need to run a Unicorn sync. The following fields did not match the serialized version:\n"); foreach (var item in results) { if (results.Count > 1) { sb.AppendFormat("\n{0}: {1}", item.Key.DisplayName, string.Join(", ", item.Value)); } else { sb.AppendFormat("\n{0}", string.Join(", ", item.Value)); } } sb.Append("\n\nDo you want to overwrite anyway?\nTHIS MAY CAUSE LOST WORK."); return(sb.ToString()); } catch (Exception ex) { Log.Error("Exception occurred while performing serialization conflict check!", ex, this); return("Exception occurred: " + ex.Message); // this will cause a few retries } }
protected override string GetDialogText(SaveArgs args) { var results = new Dictionary <Item, IList <string> >(); foreach (var item in args.Items) { // we grab the existing item from the database. This will NOT include the changed values we're saving. // this is because we want to verify that the base state of the item matches serialized, NOT the state we're saving. // if the base state and the serialized state match we can be pretty sure that the changes we are writing won't clobber anything serialized but not synced Item existingItem = Client.ContentDatabase.GetItem(item.ID, item.Language, item.Version); Assert.IsNotNull(existingItem, "Existing item {0} did not exist! This should never occur.", item.ID); var existingSitecoreItem = new ItemData(existingItem); foreach (var configuration in _configurations) { // ignore conflict checks if Transparent Sync is turned on (in which case this is a tautology - 'get from database' would be 'get from disk' so it always matches if (configuration.Resolve <IUnicornDataProviderConfiguration>().EnableTransparentSync) { continue; } var result = configuration.Resolve <IPredicate>().Includes(existingSitecoreItem); // ignore conflicts on items that Unicorn is not managing if (!result.IsIncluded) { continue; } // If a Field Value Manipulator is attached to the predicate, we _expect_ Serialized content to be different from what is in Sitecore if (result.FieldValueManipulator != null) { continue; } // evaluator signals that it does not care about conflicts (e.g. NIO) if (!configuration.Resolve <IEvaluator>().ShouldPerformConflictCheck(existingItem)) { continue; } IItemData serializedItemData = configuration.Resolve <ITargetDataStore>().GetByPathAndId(existingSitecoreItem.Path, existingSitecoreItem.Id, existingSitecoreItem.DatabaseName); // not having an existing serialized version means no possibility of conflict here if (serializedItemData == null) { continue; } var fieldFilter = configuration.Resolve <IFieldFilter>(); var itemComparer = configuration.Resolve <IItemComparer>(); var fieldIssues = GetFieldSyncStatus(existingSitecoreItem, serializedItemData, fieldFilter, itemComparer); if (fieldIssues.Count == 0) { continue; } results[existingItem] = fieldIssues; } } // no problems if (results.Count == 0) { return(null); } var sb = new StringBuilder(); if (About.Version.StartsWith("7") || About.Version.StartsWith("6")) { // older Sitecores used \n to format dialog text sb.Append("CRITICAL MESSAGE FROM UNICORN:\n"); sb.Append("You need to run a Unicorn sync. The following fields did not match the serialized version:\n"); foreach (var item in results) { if (results.Count > 1) { sb.AppendFormat("\n{0}: {1}", item.Key.DisplayName, string.Join(", ", item.Value)); } else { sb.AppendFormat("\n{0}", string.Join(", ", item.Value)); } } sb.Append("\n\nDo you want to overwrite anyway?\nTHIS MAY CAUSE LOST WORK."); } else { // Sitecore 8.x+ uses HTML to format dialog text sb.Append("<p style=\"font-weight: bold; margin-bottom: 1em;\">CRITICAL MESSAGE FROM UNICORN:</p>"); sb.Append("<p>You need to run a Unicorn sync. The following fields did not match the serialized version:</p><ul style=\"margin: 1em 0\">"); foreach (var item in results) { if (results.Count > 1) { sb.AppendFormat("<li>{0}: {1}</li>", item.Key.DisplayName, string.Join(", ", item.Value)); } else { sb.AppendFormat("<li>{0}</li>", string.Join(", ", item.Value)); } } sb.Append("</ul><p>Do you want to overwrite anyway?<br>THIS MAY CAUSE LOST WORK.</p>"); } return(sb.ToString()); }
public virtual void SaveItem(ItemDefinition itemDefinition, ItemChanges changes, CallContext context) { if (DisableSerialization) return; Assert.ArgumentNotNull(itemDefinition, "itemDefinition"); Assert.ArgumentNotNull(changes, "changes"); // get the item we're saving to evaluate with the predicate // NOTE: the item in this state may be incomplete as Sitecore can sometimes send partial item data and rely on changes to do the save // e.g. during package installations. So we have to merge the changes with any existing item data if we save it later, to keep it consistent. IItemData sourceItem = new ItemData(changes.Item); if (!_predicate.Includes(sourceItem).IsIncluded) return; string oldName = changes.Renamed ? changes.Properties["name"].OriginalValue.ToString() : string.Empty; if (changes.Renamed && !oldName.Equals(sourceItem.Name, StringComparison.Ordinal)) // it's a rename, in which the name actually changed (template builder will cause 'renames' for the same name!!!) { using (new DatabaseCacheDisabler()) { // disabling the DB caches while running this ensures that any children of the renamed item are retrieved with their proper post-rename paths and thus are not saved at their old location // this allows us to filter out any excluded children by predicate when the data store moves children var predicatedItem = new PredicateFilteredItemData(sourceItem, _predicate); _targetDataStore.MoveOrRenameItem(predicatedItem, changes.Item.Paths.ParentPath + "/" + oldName); } _logger.RenamedItem(_targetDataStore.FriendlyName, sourceItem, oldName); } else if (HasConsequentialChanges(changes)) // it's a simple update - but we reject it if only inconsequential fields (last updated, revision) were changed - again, template builder FTW { var existingSerializedItem = _targetDataStore.GetByPathAndId(sourceItem.Path, sourceItem.Id, sourceItem.DatabaseName); // generated an IItemData from the item changes we received, and apply those changes to the existing serialized item if any if (existingSerializedItem != null) sourceItem = new ItemChangeApplyingItemData(existingSerializedItem, changes); else sourceItem = new ItemChangeApplyingItemData(changes); _targetDataStore.Save(sourceItem); AddBlobsToCache(sourceItem); _logger.SavedItem(_targetDataStore.FriendlyName, sourceItem, "Saved"); } }