/// <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);
                }
            });
        }
Exemple #2
0
        private static bool IsDocumentError(CouchbaseLiteException e)
        {
            if (e == null)
            {
                return(false);
            }

            return(e.Code == StatusCode.NotFound || e.Code == StatusCode.Forbidden);
        }
Exemple #3
0
        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");
        }
Exemple #4
0
        // 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));
        }
Exemple #5
0
        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");
            }
        }
Exemple #6
0
        /// <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();
        }
Exemple #7
0
        // 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);
        }
Exemple #8
0
        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);
        }
Exemple #9
0
        /// <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();
        }
Exemple #10
0
        // 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);
        }