/// <summary> /// Uploads the revision as JSON instead of multipart. /// </summary> /// <remarks> /// Fallback to upload a revision if UploadMultipartRevision failed due to the server's rejecting /// multipart format. /// </remarks> /// <param name="rev">Rev.</param> private void UploadJsonRevision(RevisionInternal rev) { // Get the revision's properties: if (!LocalDatabase.InlineFollowingAttachmentsIn(rev)) { LastError = new CouchbaseLiteException(StatusCode.BadAttachment); RevisionFailed(); return; } var path = string.Format("/{0}?new_edits=false", Uri.EscapeUriString(rev.GetDocId())); SendAsyncRequest(HttpMethod.Put, path, rev.GetProperties(), (result, e) => { if (e != null) { LastError = e; RevisionFailed(); } else { Log.V(TAG, "Sent {0} (JSON), response={1}", rev, result); SafeIncrementCompletedChangesCount(); RemovePending(rev); } }); }
private static bool IsDocumentError(CouchbaseLiteException e) { if (e == null) { return(false); } return(e.Code == StatusCode.NotFound || e.Code == StatusCode.Forbidden); }
public void TestCreateWithEmptyDBNames() { CouchbaseLiteException e = null;; try { OpenDB(""); } catch (CouchbaseLiteException ex) { e = ex; ex.Error.Should().Be(CouchbaseLiteError.WrongFormat, "because the database cannot have an empty name"); ex.Domain.Should().Be(CouchbaseLiteErrorType.CouchbaseLite, "because this is a LiteCore error"); } e.Should().NotBeNull("because an exception is expected"); }
// Fallback to upload a revision if uploadMultipartRevision failed due to the server's rejecting // multipart format. private void UploadJsonRevision(RevisionInternal rev) { // Get the revision's properties: if (!db.InlineFollowingAttachmentsIn(rev)) { error = new CouchbaseLiteException(Status.BadAttachment); RevisionFailed(); return; } Log.V(Log.TagSync, "%s | %s: uploadJsonRevision() calling asyncTaskStarted()", this , Sharpen.Thread.CurrentThread()); AsyncTaskStarted(); string path = string.Format("/%s?new_edits=false", URIUtils.Encode(rev.GetDocId() )); SendAsyncRequest("PUT", path, rev.GetProperties(), new _RemoteRequestCompletionBlock_594 (this, rev)); }
public void TestDeleteOpeningDBByStaticMethod() { var dir = Directory; var options = new DatabaseConfiguration { Directory = dir }; using (var db = new Database("db", options)) { CouchbaseLiteException e = null; try { Database.Delete("db", dir); } catch (CouchbaseLiteException ex) { e = ex; ex.Error.Should().Be(CouchbaseLiteError.Busy, "because an in-use database cannot be deleted"); ex.Domain.Should().Be(CouchbaseLiteErrorType.CouchbaseLite, "because this is a LiteCore error"); } e.Should().NotBeNull("because an exception is expected"); } }
/// <summary>This will be called when _revsToInsert fills up:</summary> private void InsertDownloads(IList <RevisionInternal> downloads) { Log.To.SyncPerf.I(TAG, "{0} inserting {1} revisions into db...", this, downloads.Count); Log.To.Sync.V(TAG, "{0} inserting {1} revisions...", this, downloads.Count); var time = DateTime.UtcNow; downloads.Sort(new RevisionComparer()); if (!LocalDatabase.IsOpen) { return; } try { var success = LocalDatabase.RunInTransaction(() => { foreach (var rev in downloads) { var fakeSequence = rev.Sequence; rev.Sequence = 0L; var history = Database.ParseCouchDBRevisionHistory(rev.GetProperties()); if ((history == null || history.Count == 0) && rev.Generation > 1) { Log.To.Sync.W(TAG, "{0} missing revision history in response for: {0}", this, rev); LastError = new CouchbaseLiteException(StatusCode.UpStreamError); RevisionFailed(); continue; } Log.To.Sync.V(TAG, String.Format("Inserting {0} {1}", new SecureLogString(rev.DocID, LogMessageSensitivity.PotentiallyInsecure), new LogJsonString(history))); // Insert the revision: try { LocalDatabase.ForceInsert(rev, history, RemoteUrl); } catch (CouchbaseLiteException e) { if (e.Code == StatusCode.Forbidden) { Log.To.Sync.I(TAG, "{0} remote rev failed validation: {1}", this, rev); } else if (e.Code == StatusCode.AttachmentError) { // Revision with broken _attachments metadata (i.e. bogus revpos) // should not stop replication. Warn and skip it. Log.To.Sync.W(TAG, "{0} revision {1} has invalid attachment metadata: {2}", this, rev, new SecureLogJsonString(rev.GetAttachments(), LogMessageSensitivity.PotentiallyInsecure)); } else if (e.Code == StatusCode.DbBusy) { Log.To.Sync.I(TAG, "Database is busy, will retry soon..."); // abort transaction; RunInTransaction will retry return(false); } else { Log.To.Sync.W(TAG, "{0} failed to write {1}: status={2}", this, rev, e.Code); RevisionFailed(); LastError = e; continue; } } catch (Exception e) { throw Misc.CreateExceptionAndLog(Log.To.Sync, e, TAG, "Error inserting downloads"); } _pendingSequences.RemoveSequence(fakeSequence); } Log.To.Sync.V(TAG, "{0} finished inserting {1} revisions", this, downloads.Count); return(true); }); Log.To.Sync.V(TAG, "Finished inserting {0} revisions. Success == {1}", downloads.Count, success); } catch (Exception e) { Log.To.Sync.E(TAG, "Exception inserting revisions, continuing...", e); } // Checkpoint: LastSequence = _pendingSequences.GetCheckpointedValue(); var delta = (DateTime.UtcNow - time).TotalMilliseconds; Log.To.Sync.I(TAG, "Inserted {0} revs in {1} milliseconds", downloads.Count, delta); Log.To.SyncPerf.I(TAG, "Inserted {0} revs in {1} milliseconds", downloads.Count, delta); SafeAddToCompletedChangesCount(downloads.Count); PauseOrResume(); }
// Get a bunch of revisions in one bulk request. Will use _bulk_get if possible. private void PullBulkRevisions(IList <RevisionInternal> bulkRevs) { var nRevs = bulkRevs == null ? 0 : bulkRevs.Count; if (nRevs == 0) { return; } Log.To.Sync.I(TAG, "{0} bulk-fetching {1} remote revisions...", ReplicatorID, nRevs); if (!_canBulkGet) { PullBulkWithAllDocs(bulkRevs); return; } Log.To.SyncPerf.I(TAG, "{0} bulk-getting {1} remote revisions...", ReplicatorID, nRevs); var remainingRevs = new List <RevisionInternal>(bulkRevs); BulkDownloader dl = new BulkDownloader(new BulkDownloaderOptions { ClientFactory = ClientFactory, DatabaseUri = RemoteUrl, Revisions = bulkRevs, Database = LocalDatabase, RequestHeaders = RequestHeaders, RetryStrategy = ReplicationOptions.RetryStrategy, CookieStore = CookieContainer }); dl.DocumentDownloaded += (sender, args) => { var props = args.DocumentProperties; var rev = props.CblID() != null ? new RevisionInternal(props) : new RevisionInternal(props.CblID(), props.CblRev(), false); var pos = remainingRevs.IndexOf(rev); if (pos > -1) { rev.Sequence = remainingRevs[pos].Sequence; remainingRevs.RemoveAt(pos); } else { Log.To.Sync.W(TAG, "Received unexpected rev {0}; ignoring", rev); return; } if (props.CblID() != null) { // Add to batcher ... eventually it will be fed to -insertRevisions:. QueueDownloadedRevision(rev); } else { var status = StatusFromBulkDocsResponseItem(props); Log.To.Sync.W(TAG, "Error downloading {0}", rev); var error = new CouchbaseLiteException(status.Code); LastError = error; RevisionFailed(); SafeIncrementCompletedChangesCount(); if (IsDocumentError(error)) { _pendingSequences.RemoveSequence(rev.Sequence); } } }; dl.Complete += (sender, args) => { if (args != null && args.Error != null) { RevisionFailed(); if (remainingRevs.Count == 0) { LastError = args.Error; } } else if (remainingRevs.Count > 0) { Log.To.Sync.W(TAG, "{0} revs not returned from _bulk_get: {1}", remainingRevs.Count, remainingRevs); for (int i = 0; i < remainingRevs.Count; i++) { var rev = remainingRevs[i]; if (ShouldRetryDownload(rev.DocID)) { _bulkRevsToPull.Add(remainingRevs[i]); } else { LastError = args.Error; SafeIncrementCompletedChangesCount(); } } } SafeAddToCompletedChangesCount(remainingRevs.Count); LastSequence = _pendingSequences.GetCheckpointedValue(); Misc.SafeDispose(ref dl); PullRemoteRevisions(); }; dl.Authenticator = Authenticator; WorkExecutor.StartNew(dl.Start, CancellationTokenSource.Token, TaskCreationOptions.None, WorkExecutor.Scheduler); }
public void ProcessLoginResponse(IDictionary <string, object> jsonResponse, HttpRequestHeaders headers, Exception error, Action <bool, Exception> continuation) { if (error != null && !Misc.IsUnauthorizedError(error)) { // If there's some non-401 error, just pass it on continuation(false, error); return; } if (RefreshToken != null || _authUrl != null) { // Logging in with an authUrl from the OP, or refreshing the ID token: if (error != null) { _authUrl = null; if (RefreshToken != null) { // Refresh failed; go back to login state: RefreshToken = null; Username = null; DeleteTokens(); continuation(true, null); return; } } else { // Generated or freshed ID token: if (ParseTokens(jsonResponse)) { Log.To.Sync.I(Tag, "{0}: logged in as {1}", this, Username); SaveTokens(jsonResponse); } else { error = new CouchbaseLiteException("Server didn't return a refreshed ID token", StatusCode.UpStreamError); } } } else { // Login challenge: get the info & ask the app callback to log into the OP: var login = default(string); var challenge = error?.Data?["AuthChallenge"]?.AsDictionary <string, string>(); if (challenge?.Get("Scheme") == "OIDC") { login = challenge.Get("login"); } if (login != null) { Log.To.Sync.I(Tag, "{0} got OpenID Connect login URL: {1}", this, login); ContinueAsyncLogin(new Uri(login), continuation); return; // don't call the continuation block yet } else { error = new CouchbaseLiteException("Server didn't provide an OpenID login URL", StatusCode.UpStreamError); } } // by default, keep going immediately continuation(false, error); }
/// <summary>This will be called when _revsToInsert fills up:</summary> private void InsertDownloads(IList <RevisionInternal> downloads) { Log.V(TAG, "Inserting {0} revisions...", downloads.Count); var time = DateTime.UtcNow; downloads.Sort(new RevisionComparer()); if (LocalDatabase == null) { return; } try { var success = LocalDatabase.RunInTransaction(() => { foreach (var rev in downloads) { var fakeSequence = rev.GetSequence(); rev.SetSequence(0L); var history = Database.ParseCouchDBRevisionHistory(rev.GetProperties()); if (history.Count == 0 && rev.GetGeneration() > 1) { Log.W(TAG, "Missing revision history in response for: {0}", rev); LastError = new CouchbaseLiteException(StatusCode.UpStreamError); RevisionFailed(); continue; } Log.V(TAG, String.Format("Inserting {0} {1}", rev.GetDocId(), Manager.GetObjectMapper().WriteValueAsString(history))); // Insert the revision: try { LocalDatabase.ForceInsert(rev, history, RemoteUrl); } catch (CouchbaseLiteException e) { if (e.Code == StatusCode.Forbidden) { Log.I(TAG, "Remote rev failed validation: " + rev); } else if (e.Code == StatusCode.DbBusy) { // abort transaction; RunInTransaction will retry return(false); } else { Log.W(TAG, " failed to write {0}: status={1}", rev, e.Code); RevisionFailed(); LastError = e; continue; } } catch (Exception e) { Log.E(TAG, "Exception inserting downloads.", e); throw; } _pendingSequences.RemoveSequence(fakeSequence); } Log.D(TAG, " Finished inserting " + downloads.Count + " revisions"); return(true); }); Log.V(TAG, "Finished inserting {0} revisions. Success == {1}", downloads.Count, success); } catch (Exception e) { Log.E(TAG, "Exception inserting revisions", e); } // Checkpoint: LastSequence = _pendingSequences.GetCheckpointedValue(); var delta = (DateTime.UtcNow - time).TotalMilliseconds; Log.D(TAG, "Inserted {0} revs in {1} milliseconds", downloads.Count, delta); var newCompletedChangesCount = CompletedChangesCount + downloads.Count; Log.D(TAG, "InsertDownloads() updating CompletedChangesCount from {0} -> {1}", CompletedChangesCount, newCompletedChangesCount); SafeAddToCompletedChangesCount(downloads.Count); PauseOrResume(); }
// Get a bunch of revisions in one bulk request. Will use _bulk_get if possible. private void PullBulkRevisions(IList <RevisionInternal> bulkRevs) { var nRevs = bulkRevs == null ? 0 : bulkRevs.Count; if (nRevs == 0) { return; } Log.D(TAG, "{0} bulk-fetching {1} remote revisions...", this, nRevs); Log.V(TAG, "{0} bulk-fetching remote revisions: {1}", this, bulkRevs); if (!_canBulkGet) { PullBulkWithAllDocs(bulkRevs); return; } Log.V(TAG, "POST _bulk_get"); var remainingRevs = new List <RevisionInternal>(bulkRevs); ++_httpConnectionCount; BulkDownloader dl; try { dl = new BulkDownloader(WorkExecutor, ClientFactory, RemoteUrl, bulkRevs, LocalDatabase, RequestHeaders); dl.DocumentDownloaded += (sender, args) => { var props = args.DocumentProperties; var rev = props.Get("_id") != null ? new RevisionInternal(props) : new RevisionInternal(props.GetCast <string> ("id"), props.GetCast <string> ("rev"), false); var pos = remainingRevs.IndexOf(rev); if (pos > -1) { rev.SetSequence(remainingRevs[pos].GetSequence()); remainingRevs.RemoveAt(pos); } else { Log.W(TAG, "Received unexpected rev {0}; ignoring", rev); return; } if (props.GetCast <string>("_id") != null) { // Add to batcher ... eventually it will be fed to -insertRevisions:. QueueDownloadedRevision(rev); } else { var status = StatusFromBulkDocsResponseItem(props); LastError = new CouchbaseLiteException(status.Code); RevisionFailed(); SafeIncrementCompletedChangesCount(); } }; dl.Complete += (sender, args) => { if (args != null && args.Error != null) { RevisionFailed(); if (remainingRevs.Count == 0) { LastError = args.Error; } } else if (remainingRevs.Count > 0) { Log.W(TAG, "{0} revs not returned from _bulk_get: {1}", remainingRevs.Count, remainingRevs); for (int i = 0; i < remainingRevs.Count; i++) { var rev = remainingRevs[i]; if (ShouldRetryDownload(rev.GetDocId())) { _bulkRevsToPull.Add(remainingRevs[i]); } else { LastError = args.Error; SafeIncrementCompletedChangesCount(); } } } SafeAddToCompletedChangesCount(remainingRevs.Count); --_httpConnectionCount; PullRemoteRevisions(); }; } catch (Exception) { return; } dl.Authenticator = Authenticator; WorkExecutor.StartNew(dl.Run, CancellationTokenSource.Token, TaskCreationOptions.None, WorkExecutor.Scheduler); }
public void ProcessLoginResponse(IDictionary<string, object> jsonResponse, HttpRequestHeaders headers, Exception error, Action<bool, Exception> continuation) { if(error != null && !Misc.IsUnauthorizedError(error)) { // If there's some non-401 error, just pass it on continuation(false, error); return; } if(RefreshToken != null || _authUrl != null) { // Logging in with an authUrl from the OP, or refreshing the ID token: if(error != null) { _authUrl = null; if(RefreshToken != null) { // Refresh failed; go back to login state: RefreshToken = null; Username = null; DeleteTokens(); continuation(true, null); return; } } else { // Generated or freshed ID token: if(ParseTokens(jsonResponse)) { Log.To.Sync.I(Tag, "{0}: logged in as {1}", this, Username); SaveTokens(jsonResponse); } else { error = new CouchbaseLiteException("Server didn't return a refreshed ID token", StatusCode.UpStreamError); } } } else { // Login challenge: get the info & ask the app callback to log into the OP: var login = default(string); var challenge = error?.Data?["AuthChallenge"]?.AsDictionary<string, string>(); if(challenge?.Get("Scheme") == "OIDC") { login = challenge.Get("login"); } if(login != null) { Log.To.Sync.I(Tag, "{0} got OpenID Connect login URL: {1}", this, login); ContinueAsyncLogin(new Uri(login), continuation); return; // don't call the continuation block yet } else { error = new CouchbaseLiteException("Server didn't provide an OpenID login URL", StatusCode.UpStreamError); } } // by default, keep going immediately continuation(false, error); }