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); } }
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)); }
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); } } }