private Document ResolveFunc(Conflict conflict)
 {
     if (conflict.RemoteDocument == null || conflict.LocalDocument == null)
     {
         return(null);
     }
     else if (conflict.LocalDocument.Generation > conflict.RemoteDocument.Generation)
     {
         return(conflict.LocalDocument);
     }
     else if (conflict.LocalDocument.Generation < conflict.RemoteDocument.Generation)
     {
         return(conflict.RemoteDocument);
     }
     else
     {
         return(String.CompareOrdinal(conflict.LocalDocument.RevID, conflict.RemoteDocument.RevID) > 0
             ? conflict.LocalDocument : conflict.RemoteDocument);
     }
 }
Пример #2
0
        internal void ResolveConflict([NotNull] string docID, [NotNull] IConflictResolver resolver)
        {
            Debug.Assert(docID != null);

            Document doc = null, otherDoc = null, baseDoc = null;
            var      inConflict = true;

            while (inConflict)
            {
                ThreadSafety.DoLocked(() =>
                {
                    LiteCoreBridge.Check(err => Native.c4db_beginTransaction(_c4db, err));
                    try {
                        doc = new Document(this, docID);
                        if (!doc.Exists)
                        {
                            doc.Dispose();
                            return;
                        }

                        otherDoc = new Document(this, docID);
                        if (!otherDoc.Exists)
                        {
                            doc.Dispose();
                            otherDoc.Dispose();
                            return;
                        }

                        otherDoc.SelectConflictingRevision();
                        baseDoc = new Document(this, docID);
                        if (!baseDoc.SelectCommonAncestor(doc, otherDoc) || baseDoc.ToDictionary() == null)
                        {
                            baseDoc.Dispose();
                            baseDoc = null;
                        }

                        LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, true, err));
                    } catch (Exception) {
                        doc?.Dispose();
                        otherDoc?.Dispose();
                        baseDoc?.Dispose();
                        LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, false, err));
                    }
                });

                var conflict = new Conflict(doc, otherDoc, baseDoc);
                var logID    = new SecureLogString(doc.Id, LogMessageSensitivity.PotentiallyInsecure);
                Log.To.Database.I(Tag, $"Resolving doc '{logID}' with {resolver.GetType().Name} (mine={doc.RevID}, theirs={otherDoc.RevID}, base={baseDoc?.RevID})");
                Document resolved = null;
                try {
                    resolved = resolver.Resolve(conflict);
                    if (resolved == null)
                    {
                        throw new LiteCoreException(new C4Error(C4ErrorCode.Conflict));
                    }

                    SaveResolvedDocument(resolved, conflict);
                    inConflict = false;
                } catch (LiteCoreException e) {
                    if (e.Error.domain == C4ErrorDomain.LiteCoreDomain && e.Error.code == (int)C4ErrorCode.Conflict)
                    {
                        continue;
                    }

                    throw;
                } finally {
                    resolved?.Dispose();
                    if (resolved != doc)
                    {
                        doc?.Dispose();
                    }

                    if (resolved != otherDoc)
                    {
                        otherDoc?.Dispose();
                    }

                    if (resolved != baseDoc)
                    {
                        baseDoc?.Dispose();
                    }
                }
            }
        }
 /// <summary>
 /// The callback default conflict resolve method, if conflict occurs.
 /// </summary>
 public Document Resolve(Conflict conflict)
 {
     return(ResolveFunc(conflict));
 }
Пример #4
0
        private Document Save([NotNull] Document document, bool deletion)
        {
            if (document.IsInvalidated)
            {
                throw new CouchbaseLiteException(StatusCode.NotAllowed, "Cannot save or delete a MutableDocument that has already been used to save or delete");
            }

            if (deletion && document.RevID == null)
            {
                throw new CouchbaseLiteException(StatusCode.NotAllowed, "Cannot delete a document that has not yet been saved");
            }

            var         docID = document.Id;
            var         doc = document;
            Document    baseDoc = null, otherDoc = null;
            C4Document *newDoc = null;
            Document    retVal = null;

            while (true)
            {
                var resolve = false;
                retVal = ThreadSafety.DoLocked(() =>
                {
                    VerifyDB(doc);
                    LiteCoreBridge.Check(err => Native.c4db_beginTransaction(_c4db, err));
                    try {
                        if (deletion)
                        {
                            // Check for no-op case if the document does not exist
                            var curDoc = (C4Document *)NativeHandler.Create().AllowError(new C4Error(C4ErrorCode.NotFound))
                                         .Execute(err => Native.c4doc_get(_c4db, docID, true, err));
                            if (curDoc == null)
                            {
                                (document as MutableDocument)?.MarkAsInvalidated();
                                return(null);
                            }

                            Native.c4doc_free(curDoc);
                        }

                        var newDocOther = newDoc;
                        Save(doc, &newDocOther, baseDoc?.c4Doc?.HasValue == true ? baseDoc.c4Doc.RawDoc : null, deletion);
                        if (newDocOther != null)
                        {
                            // Save succeeded, so commit
                            newDoc = newDocOther;
                            LiteCoreBridge.Check(err =>
                            {
                                var success = Native.c4db_endTransaction(_c4db, true, err);
                                if (!success)
                                {
                                    Native.c4doc_free(newDoc);
                                }

                                return(success);
                            });

                            (document as MutableDocument)?.MarkAsInvalidated();
                            baseDoc?.Dispose();
                            return(new Document(this, docID, new C4DocumentWrapper(newDoc)));
                        }

                        // There was a conflict
                        if (deletion && !doc.IsDeleted)
                        {
                            var deletedDoc = doc.ToMutable();
                            deletedDoc.MarkAsDeleted();
                            doc = deletedDoc;
                        }

                        if (doc.c4Doc != null)
                        {
                            baseDoc = new Document(this, docID, doc.c4Doc.Retain <C4DocumentWrapper>());
                        }

                        otherDoc = new Document(this, docID);
                        if (!otherDoc.Exists)
                        {
                            LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, false, err));
                            return(null);
                        }
                    } catch (Exception) {
                        baseDoc?.Dispose();
                        otherDoc?.Dispose();
                        LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, false, err));
                        throw;
                    }

                    resolve = true;
                    LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, false, err));
                    return(null);
                });

                if (!resolve)
                {
                    return(retVal);
                }

                // Resolve Conflict
                Document resolved = null;
                try {
                    var resolver = Config.ConflictResolver;
                    var conflict = new Conflict(doc, otherDoc, baseDoc);
                    resolved = resolver.Resolve(conflict);
                    if (resolved == null)
                    {
                        throw new LiteCoreException(new C4Error(C4ErrorCode.Conflict));
                    }
                } finally {
                    baseDoc?.Dispose();
                    if (!ReferenceEquals(resolved, otherDoc))
                    {
                        otherDoc?.Dispose();
                    }
                }

                retVal = ThreadSafety.DoLocked(() =>
                {
                    var current = new Document(this, docID);
                    if (resolved.RevID == current.RevID)
                    {
                        (document as MutableDocument)?.MarkAsInvalidated();
                        current.Dispose();
                        return(resolved); // Same as current
                    }

                    // For saving
                    doc      = resolved;
                    baseDoc  = current;
                    deletion = resolved.IsDeleted;
                    return(null);
                });

                if (retVal != null)
                {
                    return(retVal);
                }
            }
        }