private async Task <IReadOnlyCollection <ChangeItem> > ApplyRemoveAsync([NotNull] IEntry entry, [NotNull] IReadOnlyDictionary <XName, IUntypedReadableProperty> properties, [NotNull] propremove remove, bool previouslyFailed, CancellationToken cancellationToken)
        {
            var result = new List <ChangeItem>();

            if (remove.prop?.Any == null)
            {
                return(result);
            }

            var language = remove.prop.Language;

            var failed = previouslyFailed;

            foreach (var element in remove.prop.Any)
            {
                // Add a parent elements xml:lang to the element
                var elementLanguage = element.Attribute(XNamespace.Xml + "lang")?.Value;
                if (string.IsNullOrEmpty(elementLanguage) && !string.IsNullOrEmpty(language))
                {
                    element.SetAttributeValue(XNamespace.Xml + "lang", language);
                }
                var propertyKey = element.Name;

                if (failed)
                {
                    result.Add(ChangeItem.FailedDependency(propertyKey));
                    continue;
                }

                var property = FindProperty(properties, propertyKey);
                if (property != null)
                {
                    if (!(property is IUntypedWriteableProperty))
                    {
                        result.Add(ChangeItem.ReadOnly(property, element, "Cannot remove protected property"));
                    }
                    else if (entry.FileSystem.PropertyStore == null)
                    {
                        if (property is IDeadProperty)
                        {
                            result.Add(ChangeItem.ReadOnly(property, element, "Cannot remove dead without property store"));
                        }
                        else
                        {
                            result.Add(ChangeItem.ReadOnly(property, element, "Cannot remove live property"));
                        }
                    }
                    else if (property is ILiveProperty)
                    {
                        result.Add(ChangeItem.Failed(property, "Cannot remove live property"));
                    }
                    else
                    {
                        try
                        {
                            var oldValue = await property.GetXmlValueAsync(cancellationToken).ConfigureAwait(false);

                            var success = await entry.FileSystem.PropertyStore.RemoveAsync(entry, propertyKey, cancellationToken).ConfigureAwait(false);

                            // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
                            if (!success)
                            {
                                result.Add(ChangeItem.Failed(property, "Couldn't remove property from property store (concurrent access?)"));
                            }
                            else
                            {
                                result.Add(ChangeItem.Removed(property, oldValue));
                            }
                        }
                        catch (Exception ex)
                        {
                            result.Add(ChangeItem.Failed(property, ex.Message));
                            failed = true;
                        }
                    }
                }
                else
                {
                    result.Add(ChangeItem.Removed(propertyKey));
                }
            }

            return(result);
        }
        private async Task <IReadOnlyCollection <ChangeItem> > ApplySetAsync([NotNull] IEntry entry, [NotNull] Dictionary <XName, IUntypedReadableProperty> properties, [NotNull] propset set, bool previouslyFailed, CancellationToken cancellationToken)
        {
            var result = new List <ChangeItem>();

            if (set.prop?.Any == null)
            {
                return(result);
            }

            var language = set.prop.Language;

            var failed = previouslyFailed;

            foreach (var element in set.prop.Any)
            {
                // Add a parent elements xml:lang to the element
                var elementLanguage = element.Attribute(XNamespace.Xml + "lang")?.Value;
                if (string.IsNullOrEmpty(elementLanguage) && !string.IsNullOrEmpty(language))
                {
                    element.SetAttributeValue(XNamespace.Xml + "lang", language);
                }

                if (failed)
                {
                    result.Add(ChangeItem.FailedDependency(element.Name));
                    continue;
                }

                var property = FindProperty(properties, element.Name);
                if (property != null)
                {
                    ChangeItem changeItem;
                    try
                    {
                        var writeableProperty = property as IUntypedWriteableProperty;
                        if (writeableProperty != null)
                        {
                            if (entry.FileSystem.PropertyStore == null && writeableProperty is IDeadProperty)
                            {
                                changeItem = ChangeItem.ReadOnly(property, element, "Cannot modify dead without property store");
                            }
                            else
                            {
                                var oldValue = await writeableProperty
                                               .GetXmlValueAsync(cancellationToken)
                                               .ConfigureAwait(false);

                                await writeableProperty
                                .SetXmlValueAsync(element, cancellationToken)
                                .ConfigureAwait(false);

                                changeItem = ChangeItem.Modified(property, element, oldValue);
                            }
                        }
                        else
                        {
                            changeItem = ChangeItem.ReadOnly(property, element, "Cannot modify protected property");
                        }
                    }
                    catch (Exception ex)
                    {
                        changeItem = ChangeItem.Failed(property, ex.Message);
                    }

                    failed = !changeItem.IsSuccess;
                    result.Add(changeItem);
                }
                else
                {
                    if (entry.FileSystem.PropertyStore == null)
                    {
                        result.Add(ChangeItem.InsufficientStorage(element, "Cannot add dead property without property store"));
                        failed = true;
                    }
                    else
                    {
                        var newProperty = new DeadProperty(entry.FileSystem.PropertyStore, entry, element);
                        properties.Add(newProperty.Name, newProperty);
                        await newProperty.SetXmlValueAsync(element, cancellationToken)
                        .ConfigureAwait(false);

                        result.Add(ChangeItem.Added(newProperty, element));
                    }
                }
            }

            return(result);
        }
        private async Task <IReadOnlyCollection <ChangeItem> > RevertChangesAsync([NotNull] IEntry entry, [NotNull][ItemNotNull] IReadOnlyCollection <ChangeItem> changes, [NotNull] IDictionary <XName, IUntypedReadableProperty> properties, CancellationToken cancellationToken)
        {
            if (entry.FileSystem.PropertyStore == null || _fileSystem.PropertyStore == null)
            {
                throw new InvalidOperationException("The property store must be configured");
            }

            var newChangeItems = new List <ChangeItem>();

            foreach (var changeItem in changes.Reverse())
            {
                ChangeItem newChangeItem;
                switch (changeItem.Status)
                {
                case ChangeStatus.Added:
                    Debug.Assert(entry.FileSystem.PropertyStore != null, "entry.FileSystem.PropertyStore != null");
                    await entry.FileSystem.PropertyStore.RemoveAsync(entry, changeItem.Key, cancellationToken).ConfigureAwait(false);

                    newChangeItem = ChangeItem.FailedDependency(changeItem.Key);
                    properties.Remove(changeItem.Key);
                    break;

                case ChangeStatus.Modified:
                    Debug.Assert(entry.FileSystem.PropertyStore != null, "entry.FileSystem.PropertyStore != null");
                    Debug.Assert(changeItem.OldValue != null, "changeItem.OldValue != null");
                    if (changeItem.OldValue == null)
                    {
                        throw new InvalidOperationException("There must be a old value for the item to change");
                    }
                    await entry.FileSystem.PropertyStore.SetAsync(entry, changeItem.OldValue, cancellationToken).ConfigureAwait(false);

                    newChangeItem = ChangeItem.FailedDependency(changeItem.Key);
                    break;

                case ChangeStatus.Removed:
                    if (changeItem.Property != null)
                    {
                        properties.Add(changeItem.Key, changeItem.Property);
                        Debug.Assert(_fileSystem.PropertyStore != null, "_fileSystem.PropertyStore != null");
                        Debug.Assert(changeItem.OldValue != null, "changeItem.OldValue != null");
                        if (changeItem.OldValue == null)
                        {
                            throw new InvalidOperationException("There must be a old value for the item to change");
                        }
                        await _fileSystem.PropertyStore.SetAsync(entry, changeItem.OldValue, cancellationToken).ConfigureAwait(false);
                    }

                    newChangeItem = ChangeItem.FailedDependency(changeItem.Key);
                    break;

                case ChangeStatus.Conflict:
                case ChangeStatus.Failed:
                case ChangeStatus.InsufficientStorage:
                case ChangeStatus.ReadOnlyProperty:
                case ChangeStatus.FailedDependency:
                    newChangeItem = changeItem;
                    break;

                default:
                    throw new NotSupportedException();
                }

                newChangeItems.Add(newChangeItem);
            }

            newChangeItems.Reverse();
            return(newChangeItems);
        }