private void SaveResolvedDocument([NotNull] Document resolved, [NotNull] Conflict conflict) { InBatch(() => { var doc = conflict.Mine; var otherDoc = conflict.Theirs; // Figure out what revision to delete and what if anything to add string winningRevID, losingRevID; byte[] mergedBody = null; if (ReferenceEquals(resolved, otherDoc)) { winningRevID = otherDoc.RevID; losingRevID = doc.RevID; } else { winningRevID = doc.RevID; losingRevID = otherDoc.RevID; if (!ReferenceEquals(resolved, doc)) { resolved.Database = this; mergedBody = resolved.Encode(); } } // Tell LiteCore to do the resolution: var rawDoc = doc.c4Doc?.HasValue == true ? doc.c4Doc.RawDoc : null; LiteCoreBridge.Check(err => Native.c4doc_resolveConflict(rawDoc, winningRevID, losingRevID, mergedBody, err)); LiteCoreBridge.Check(err => Native.c4doc_save(rawDoc, 0, err)); var logID = new SecureLogString(doc.Id, LogMessageSensitivity.PotentiallyInsecure); Log.To.Database.I(Tag, $"Conflict resolved as doc '{logID}' rev {rawDoc->revID.CreateString()}"); }); }
private void OnDocError(C4Error error, bool pushing, [NotNull] string docID, bool transient) { if (_disposed) { return; } var logDocID = new SecureLogString(docID, LogMessageSensitivity.PotentiallyInsecure); if (!pushing && error.domain == C4ErrorDomain.LiteCoreDomain && error.code == (int)C4ErrorCode.Conflict) { // Conflict pulling a document -- the revision was added but app needs to resolve it: var safeDocID = new SecureLogString(docID, LogMessageSensitivity.PotentiallyInsecure); Log.To.Sync.I(Tag, $"{this} pulled conflicting version of '{safeDocID}'"); try { Config.Database.ResolveConflict(docID); } catch (Exception e) { Log.To.Sync.W(Tag, $"Conflict resolution of '{logDocID}' failed", e); } } else { var transientStr = transient ? "transient " : String.Empty; var dirStr = pushing ? "pushing" : "pulling"; Log.To.Sync.I(Tag, $"{this}: {transientStr}error {dirStr} '{logDocID}' : {error.code} ({Native.c4error_getMessage(error)})"); } }
private void OnDocEndedWithConflict(List <ReplicatedDocument> replications) { if (_disposed) { return; } for (int i = 0; i < replications.Count; i++) { var replication = replications[i]; // Conflict pulling a document -- the revision was added but app needs to resolve it: var safeDocID = new SecureLogString(replication.Id, LogMessageSensitivity.PotentiallyInsecure); WriteLog.To.Sync.I(Tag, $"{this} pulled conflicting version of '{safeDocID}'"); Task t = Task.Run(() => { try { Config.Database.ResolveConflict(replication.Id, Config.ConflictResolver); replication = replication.ClearError(); } catch (CouchbaseException e) { replication.Error = e; } catch (Exception e) { replication.Error = new CouchbaseLiteException(C4ErrorCode.UnexpectedError, e.Message, e); } if (replication.Error != null) { WriteLog.To.Sync.W(Tag, $"Conflict resolution of '{replication.Id}' failed", replication.Error); } _documentEndedUpdate.Fire(this, new DocumentReplicationEventArgs(new[] { replication }, false)); }); _conflictTasks.TryAdd(t.ContinueWith(task => _conflictTasks.TryRemove(t, out var dummy)), 0); } }
public void Purge(Document document) { CBDebug.MustNotBeNull(Log.To.Database, Tag, nameof(document), document); ThreadSafety.DoLocked(() => { CheckOpen(); VerifyDB(document); if (!document.Exists) { var docID = new SecureLogString(document.Id, LogMessageSensitivity.PotentiallyInsecure); Log.To.Database.V(Tag, $"Ignoring purge of non-existent document {docID}"); return; } InBatch(() => { var result = Native.c4doc_purgeRevision(document.c4Doc.RawDoc, null, null); if (result >= 0) { LiteCoreBridge.Check(err => Native.c4doc_save(document.c4Doc.RawDoc, 0, err)); } }); }); }
protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken) { int retryCount; do { if (Authenticator != null && response.StatusCode == HttpStatusCode.Unauthorized) { retryCount = _retryMessages.GetOrAdd(response, 0); if (retryCount >= 5) { // Multiple concurrent requests means that the Nc can sometimes get out of order // so try again, but within reason. break; } _retryMessages.TryUpdate(response, retryCount + 1, retryCount); var newRequest = new HttpRequestMessage(response.RequestMessage.Method, response.RequestMessage.RequestUri); foreach (var header in response.RequestMessage.Headers) { if (header.Key != "Authorization") { newRequest.Headers.Add(header.Key, header.Value); } } newRequest.Content = response.RequestMessage.Content; var challengeResponse = Authenticator.ResponseFromChallenge(response); if (challengeResponse != null) { newRequest.Headers.Add("Authorization", challengeResponse); return(ProcessResponse(SendAsync(newRequest, cancellationToken).Result, cancellationToken)); } } } while(false); var hasSetCookie = response.Headers.Contains("Set-Cookie"); if (hasSetCookie) { var cookie = default(Cookie); if (CookieParser.TryParse(response.Headers.GetValues("Set-Cookie").ElementAt(0), response.RequestMessage.RequestUri.Host, out cookie)) { lock (_locker) { try { _cookieStore.Add(cookie); } catch (CookieException e) { var headerValue = new SecureLogString(response.Headers.GetValues("Set-Cookie").ElementAt(0), LogMessageSensitivity.Insecure); Log.To.Sync.W("DefaultAuthHandler", $"Invalid cookie string received from remote: {headerValue}", e); } } } } _retryMessages.TryRemove(response, out retryCount); return(response); }
private void OnDocEnded(List <ReplicatedDocument> replications, bool pushing) { if (_disposed) { return; } for (int i = 0; i < replications.Count; i++) { var replication = replications[i]; var docID = replication.Id; var error = replication.NativeError; var transient = replication.IsTransient; var logDocID = new SecureLogString(docID, LogMessageSensitivity.PotentiallyInsecure); var transientStr = transient ? "transient " : String.Empty; var dirStr = pushing ? "pushing" : "pulling"; if (error.code > 0) { WriteLog.To.Sync.I(Tag, $"{this}: {transientStr}error {dirStr} '{logDocID}' : {error.code} ({Native.c4error_getMessage(error)})"); } } _documentEndedUpdate.Fire(this, new DocumentReplicationEventArgs(replications, pushing)); }
private void AddRequestHeaders(HttpRequestMessage request) { foreach (var requestHeaderKey in RequestHeaders.Keys) { if (requestHeaderKey.ToLowerInvariant() == "cookie") { Cookie cookie; var cookieStr = RequestHeaders[requestHeaderKey]; if (!CookieParser.TryParse(cookieStr, request.RequestUri.Host, out cookie)) { Log.To.Sync.W(Tag, "Invalid cookie string received, {0}", new SecureLogString(cookieStr, LogMessageSensitivity.Insecure)); } else { try { CookieStore.Add(cookie); } catch (CookieException e) { var headerValue = new SecureLogString(cookieStr, LogMessageSensitivity.Insecure); Log.To.Sync.W(Tag, $"Invalid cookie string received, {headerValue}", e); } } request.Headers.Add("Cookie", CookieStore.GetCookieHeader(request.RequestUri)); continue; } request.Headers.Add(requestHeaderKey, RequestHeaders.Get(requestHeaderKey)); } }
private void OnDocEnded(ReplicatedDocument[] replications, bool pushing) { if (_disposed) { return; } for (int i = 0; i < replications.Length; i++) { var replication = replications[i]; var docID = replication.Id; var error = replication.NativeError; var transient = replication.IsTransient; var logDocID = new SecureLogString(docID, LogMessageSensitivity.PotentiallyInsecure); if (!pushing && error.domain == C4ErrorDomain.LiteCoreDomain && error.code == (int)C4ErrorCode.Conflict) { // Conflict pulling a document -- the revision was added but app needs to resolve it: var safeDocID = new SecureLogString(docID, LogMessageSensitivity.PotentiallyInsecure); WriteLog.To.Sync.I(Tag, $"{this} pulled conflicting version of '{safeDocID}'"); try { Config.Database.ResolveConflict(docID); replications[i] = replication.ClearError(); } catch (Exception e) { WriteLog.To.Sync.W(Tag, $"Conflict resolution of '{logDocID}' failed", e); } } else { var transientStr = transient ? "transient " : String.Empty; var dirStr = pushing ? "pushing" : "pulling"; WriteLog.To.Sync.I(Tag, $"{this}: {transientStr}error {dirStr} '{logDocID}' : {error.code} ({Native.c4error_getMessage(error)})"); } } _documentEndedUpdate.Fire(this, new DocumentReplicationEventArgs(replications, pushing)); }
/// <inheritdoc /> public override string ToString() { var id = new SecureLogString(Id, LogMessageSensitivity.PotentiallyInsecure); return($"{GetType().Name}[{id}]"); }
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(); } } } }
// This is used by the listener internal Replication ReplicationWithProperties(IDictionary <string, object> properties) { // Extract the parameters from the JSON request body: // http://wiki.apache.org/couchdb/Replication bool push, createTarget; var results = new Dictionary <string, object>() { { "database", null }, { "remote", null }, { "headers", null }, { "authorizer", null } }; Status result = ParseReplicationProperties(properties, out push, out createTarget, results); if (result.IsError) { throw Misc.CreateExceptionAndLog(Log.To.Listener, result.Code, TAG, "Failed to create replication"); } object continuousObj = properties.Get("continuous"); bool continuous = false; if (continuousObj is bool) { continuous = (bool)continuousObj; } var scheduler = new SingleTaskThreadpoolScheduler(); Replication rep = null; if (push) { rep = new Pusher((Database)results["database"], (Uri)results["remote"], continuous, new TaskFactory(scheduler)); } else { rep = new Puller((Database)results["database"], (Uri)results["remote"], continuous, new TaskFactory(scheduler)); } rep.Filter = properties.Get("filter") as string; rep.FilterParams = properties.Get("query_params").AsDictionary <string, object>(); rep.DocIds = properties.Get("doc_ids").AsList <string>(); rep.Headers = new Dictionary <string, string>(); rep.ReplicationOptions = new ReplicationOptions(properties); foreach (var header in results.Get("headers").AsDictionary <string, string>()) { if (header.Key.ToLowerInvariant() == "cookie") { var cookie = default(Cookie); if (CookieParser.TryParse(header.Value, ((Uri)results["remote"]).Host, out cookie)) { try { rep.SetCookie(cookie.Name, cookie.Value, cookie.Path, cookie.Expires, cookie.Secure, cookie.HttpOnly); } catch (CookieException e) { var headerValue = new SecureLogString(header.Value, LogMessageSensitivity.Insecure); Log.To.Listener.W(TAG, $"Invalid cookie string received ({headerValue}), throwing...", e); throw new CouchbaseLiteException(StatusCode.BadRequest); } } else { var headerValue = new SecureLogString(header.Value, LogMessageSensitivity.Insecure); Log.To.Listener.W(TAG, $"Invalid cookie string received ({headerValue}), throwing..."); throw new CouchbaseLiteException(StatusCode.BadRequest); } } else { rep.Headers.Add(header.Key, header.Value); } } rep.Headers = results.Get("headers").AsDictionary <string, string>(); rep.Authenticator = results.Get("authorizer") as IAuthenticator; if (push) { ((Pusher)rep).CreateTarget = createTarget; } var db = (Database)results["database"]; // If this is a duplicate, reuse an existing replicator: var activeReplicators = default(IList <Replication>); var existing = default(Replication); if (db.ActiveReplicators.AcquireTemp(out activeReplicators)) { existing = activeReplicators.FirstOrDefault(x => x.LocalDatabase == rep.LocalDatabase && x.RemoteUrl == rep.RemoteUrl && x.IsPull == rep.IsPull && x.RemoteCheckpointDocID().Equals(rep.RemoteCheckpointDocID())); } return(existing ?? rep); }