private unsafe void DidClose(Exception e)
        {
            ResetConnections();

            C4Error c4err;

            if (e != null && !(e is ObjectDisposedException) && !(e.InnerException is ObjectDisposedException))
            {
                Log.To.Sync.I(Tag, $"WebSocket CLOSED WITH ERROR: {e}");
                Status.ConvertNetworkError(e, &c4err);
            }
            else
            {
                Log.To.Sync.I(Tag, "WebSocket CLOSED");
                c4err = new C4Error();
            }

            var c4errCopy = c4err;

            _c4Queue.DispatchAsync(() =>
            {
                if (_closed)
                {
                    Log.To.Sync.W(Tag, "Double close detected, ignoring...");
                    return;
                }

                Native.c4socket_closed(_socket, c4errCopy);
                _closed = true;
            });
        }
Example #2
0
        private unsafe void DidClose(Exception e)
        {
            if (NetworkStream == null)
            {
                return;
            }

            ResetConnections();

            C4Error c4err;

            if (e != null && !(e is ObjectDisposedException) && !(e.InnerException is ObjectDisposedException))
            {
                Log.To.Sync.I(Tag, $"WebSocket CLOSED WITH ERROR: {e}");
                Status.ConvertError(e, &c4err);
            }
            else
            {
                Log.To.Sync.I(Tag, "WebSocket CLOSED");
                c4err = new C4Error();
            }

            var c4errCopy = c4err;

            _c4Queue.DispatchAsync(() => Native.c4socket_closed(_socket, c4errCopy));
        }
        internal static CouchbaseException Create(C4Error err)
        {
            switch (err.domain)
            {
            case C4ErrorDomain.FleeceDomain:
                return(new CouchbaseFleeceException(err));

            case C4ErrorDomain.LiteCoreDomain:
                return(new CouchbaseLiteException(err));

            case C4ErrorDomain.NetworkDomain:
                return(new CouchbaseNetworkException(err));

            case C4ErrorDomain.POSIXDomain:
                return(new CouchbasePosixException(err));

            case C4ErrorDomain.SQLiteDomain:
                return(new CouchbaseSQLiteException(err));

            case C4ErrorDomain.WebSocketDomain:
                return(new CouchbaseWebsocketException(err));

            default:
                return(new CouchbaseLiteException(C4ErrorCode.UnexpectedError));
            }
        }
Example #4
0
        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 bool HandleError(C4Error error)
        {
            // If this is a transient error, or if I'm continuous and the error might go away with a change
            // in network (i.e. network down, hostname unknown), then go offline and retry later
            var transient = Native.c4error_mayBeTransient(error);

            if (!transient && !(Config.Continuous && Native.c4error_mayBeNetworkDependent(error)))
            {
                return(false); // Nope, this is permanent
            }

            if (!Config.Continuous && _retryCount >= MaxOneShotRetryCount)
            {
                return(false); //Too many retries
            }

            ClearRepl();
            if (transient)
            {
                // On transient error, retry periodically, with exponential backoff
                var delay = RetryDelay(++_retryCount);
                Log.To.Sync.I(Tag,
                              $"{this}: Transient error ({Native.c4error_getMessage(error)}); will retry in {delay}...");
                _threadSafetyQueue.DispatchAfter(Retry, delay);
            }
            else
            {
                Log.To.Sync.I(Tag,
                              $"{this}: Network error ({Native.c4error_getMessage(error)}); will retry when network changes...");
            }

            // Also retry when the network changes
            StartReachabilityObserver();
            return(true);
        }
 private unsafe void ReleaseSocket(C4Error errorIfAny)
 {
     Native.c4socket_closed(_socket, errorIfAny);
     Native.c4socket_release(_socket);
     WriteLog.To.Sync.I(Tag, $"c4Socket is closed and released, reachability is stopping monitor.");
     StopReachability();
     _closed = true;
 }
 internal ReplicatedDocument([NotNull] string docID, C4RevisionFlags flags, C4Error error,
                             bool isTransient)
 {
     Id          = docID;
     Flags       = flags.ToDocumentFlags();
     NativeError = error;
     Error       = error.domain == 0 ? null : CouchbaseException.Create(error);
     IsTransient = isTransient;
 }
        private static void OnDocError(bool pushing, string docID, C4Error error, bool transient, object context)
        {
            var replicator = context as Replicator;

            replicator?._threadSafetyQueue.DispatchAsync(() =>
            {
                replicator.OnDocError(error, pushing, docID, transient);
            });
        }
        private static string VisitCantUpgrade(C4Error err)
        {
            if (err.domain == C4ErrorDomain.LiteCoreDomain && err.code == (int)C4ErrorCode.CantUpgradeDatabase)
            {
                return
                    ($"CouchbaseLiteException ({err.domain} / {err.code}): {Native.c4error_getMessage(err)}.  If the previous database version was a version produced by a production version of Couchbase Lite, then please file a bug report at https://github.com/couchbase/couchbase-lite-net/");
            }

            return(null);
        }
        private static string VisitBugReportList(C4Error err)
        {
            if (err.domain == C4ErrorDomain.LiteCoreDomain && _BugReportErrors.Contains(err.code))
            {
                return
                    ($"CouchbaseLiteException ({err.domain} / {err.code}): {Native.c4error_getMessage(err)}.  Please file a bug report at https://github.com/couchbase/couchbase-lite-net/");
            }

            return(null);
        }
Example #11
0
        public static unsafe void ConvertError(Exception e, C4Error *outError)
        {
            var c4err = new C4Error(C4ErrorCode.RemoteError);

            switch (e)
            {
            case SocketException se:
                switch (se.SocketErrorCode)
                {
                case SocketError.HostNotFound:
                    c4err.domain = C4ErrorDomain.NetworkDomain;
                    c4err.code   = (int)C4NetworkErrorCode.UnknownHost;
                    break;

                case SocketError.HostUnreachable:
                    c4err.domain = C4ErrorDomain.NetworkDomain;
                    c4err.code   = (int)C4NetworkErrorCode.DNSFailure;
                    break;

                case SocketError.TimedOut:
                    c4err.domain = C4ErrorDomain.NetworkDomain;
                    c4err.code   = (int)C4NetworkErrorCode.Timeout;
                    break;

                case SocketError.ConnectionAborted:
                case SocketError.ConnectionReset:
                    c4err.domain = C4ErrorDomain.POSIXDomain;
                    c4err.code   = (int)PosixStatus.CONNRESET;
                    break;

                case SocketError.ConnectionRefused:
                    c4err.domain = C4ErrorDomain.POSIXDomain;
                    c4err.code   = (int)PosixStatus.CONNREFUSED;
                    break;
                }

                break;

            default:
                //HACK: System.Net.Security not available on current UWP, so it can't be used
                if (e.GetType().Name == "AuthenticationException")
                {
                    if (e.Message == "The remote certificate is invalid according to the validation procedure.")
                    {
                        c4err.domain = C4ErrorDomain.NetworkDomain;
                        c4err.code   = (int)C4NetworkErrorCode.TLSCertUntrusted;
                    }
                }

                break;
            }

            *outError = Native.c4error_make(c4err.domain, c4err.code, e.Message);
        }
        private static int MapError(C4Error err)
        {
            switch (err.domain)
            {
            case C4ErrorDomain.NetworkDomain:
                return(err.code + (int)CouchbaseLiteError.NetworkBase);

            case C4ErrorDomain.WebSocketDomain:
                return(err.code + (int)CouchbaseLiteError.HTTPBase);

            default:
                return(err.code);
            }
        }
        private static string GetMessage(C4Error err)
        {
            foreach (var visitor in MessageVisitors())
            {
                var msg = visitor(err);
                if (msg != null)
                {
                    return(msg);
                }
            }

            Debug.Assert(false, "Panic!  No suitable error message found");
            return(null);
        }
Example #14
0
        private void SetProgressLevel(C4ReplicatorProgressLevel progressLevel)
        {
            if (_repl == null)
            {
                WriteLog.To.Sync.V(Tag, $"Progress level {progressLevel} is not yet set because C4Replicator is not created.");
                return;
            }

            C4Error err = new C4Error();

            if (!Native.c4repl_setProgressLevel(_repl, progressLevel, &err) || err.code > 0)
            {
                WriteLog.To.Sync.W(Tag, $"Failed set progress level to {progressLevel}", err);
            }
        }
Example #15
0
        private bool IsPermanentError(C4Error error, out bool transient)
        {
            // If this is a transient error, or if I'm continuous and the error might go away with a change
            // in network (i.e. network down, hostname unknown), then go offline and retry later
            transient = Native.c4error_mayBeTransient(error) ||
                        (error.domain == C4ErrorDomain.WebSocketDomain && error.code ==
                         (int)C4WebSocketCustomCloseCode.WebSocketCloseUserTransient);

            if (!transient && !(Config.Continuous && Native.c4error_mayBeNetworkDependent(error)))
            {
                return(true); // Nope, this is permanent
            }

            return(false);
        }
        private static CouchbaseLiteErrorType MapDomain(C4Error err)
        {
            switch (err.domain)
            {
            case C4ErrorDomain.FleeceDomain:
                return(CouchbaseLiteErrorType.Fleece);

            case C4ErrorDomain.POSIXDomain:
                return(CouchbaseLiteErrorType.POSIX);

            case C4ErrorDomain.SQLiteDomain:
                return(CouchbaseLiteErrorType.SQLite);

            default:
                return(CouchbaseLiteErrorType.CouchbaseLite);
            }
        }
Example #17
0
        private bool HandleError(C4Error error)
        {
            if (_stopping)
            {
                Log.To.Sync.I(Tag, "Already stopping, ignoring error...");
                return(false);
            }

            // If this is a transient error, or if I'm continuous and the error might go away with a change
            // in network (i.e. network down, hostname unknown), then go offline and retry later
            var transient = Native.c4error_mayBeTransient(error) ||
                            (error.domain == C4ErrorDomain.WebSocketDomain && error.code ==
                             (int)C4WebSocketCustomCloseCode.WebSocketCloseCustomTransient);

            if (!transient && !(Config.Continuous && Native.c4error_mayBeNetworkDependent(error)))
            {
                Log.To.Sync.I(Tag, "Permanent error encountered ({0} / {1}), giving up...", error.domain, error.code);
                return(false); // Nope, this is permanent
            }

            if (!Config.Continuous && _retryCount >= MaxOneShotRetryCount)
            {
                Log.To.Sync.I(Tag, "Exceeded one-shot retry count, giving up...");
                return(false); //Too many retries
            }

            ClearRepl();
            if (transient)
            {
                // On transient error, retry periodically, with exponential backoff
                var delay = RetryDelay(++_retryCount);
                Log.To.Sync.I(Tag,
                              $"{this}: Transient error ({Native.c4error_getMessage(error)}); will retry in {delay}...");
                DispatchQueue.DispatchAfter(Retry, delay);
            }
            else
            {
                Log.To.Sync.I(Tag,
                              $"{this}: Network error ({Native.c4error_getMessage(error)}); will retry when network changes...");
            }

            // Also retry when the network changes
            StartReachabilityObserver();
            return(true);
        }
Example #18
0
        private bool HandleError(C4Error error)
        {
            if (_stopping)
            {
                WriteLog.To.Sync.I(Tag, "Already stopping, ignoring error...");
                return(false);
            }

            bool transient;

            if (IsPermanentError(error, out transient))
            {
                if (error.code > 0)
                {
                    WriteLog.To.Sync.I(Tag, "Permanent error encountered ({0} / {1}), giving up...", error.domain, error.code);
                }

                return(false);
            }

            if (!Config.Continuous && _retryCount >= MaxOneShotRetryCount)
            {
                WriteLog.To.Sync.I(Tag, "Exceeded one-shot retry count, giving up...");
                return(false); //Too many retries
            }

            ClearRepl();
            if (transient)
            {
                // On transient error, retry periodically, with exponential backoff
                var delay = RetryDelay(++_retryCount);
                WriteLog.To.Sync.I(Tag,
                                   $"{this}: Transient error ({Native.c4error_getMessage(error)}); will retry in {delay}...");
                DispatchQueue.DispatchAfter(Retry, delay);
            }
            else
            {
                WriteLog.To.Sync.I(Tag,
                                   $"{this}: Network error ({Native.c4error_getMessage(error)}); will retry when network changes...");
            }

            // Also retry when the network changes
            StartReachabilityObserver();
            return(true);
        }
Example #19
0
        public static C4Database* c4db_open(C4Slice path, C4DatabaseFlags flags, 
            C4EncryptionKey *encryptionKey, C4Error *outError)
        {
            #if DEBUG
            var retVal = _c4db_open(path, flags, encryptionKey, outError);
            if(retVal != null) {
                _AllocatedObjects[(IntPtr)retVal] = "C4Database";
                #if ENABLE_LOGGING
                Console.WriteLine("[c4db_open] Allocated 0x{0}", ((IntPtr)retVal).ToString("X"));
                #endif
            }

            return retVal;
            #else
            return _c4db_open(path, flags, encryptionKey, outError);
            #endif
        }
Example #20
0
 /// <summary>
 /// Sets a document's docType. (By convention this is the value of the "type" property of the 
 /// current revision's JSON; this value can be used as optimization when indexing a view.)
 /// The change will not be persisted until the document is saved.
 /// </summary>
 /// <param name="doc">The document to operate on</param>
 /// <param name="docType">The document type to set</param>
 /// <param name="outError">The error that occurred if the operation doesn't succeed</param>
 /// <returns>true on success, false otherwise</returns>
 public static bool c4doc_setType(C4Document *doc, string docType, C4Error *outError)
 {
     using(var docType_ = new C4String(docType)) {
         return c4doc_setType(doc, docType_.AsC4Slice(), outError);   
     }
 }
Example #21
0
 public static extern bool c4doc_save(C4Document *doc, uint maxRevTreeDepth, C4Error *outError);
Example #22
0
 public static extern int c4doc_insertRevisionWithHistory(C4Document *doc, C4Slice body, 
     [MarshalAs(UnmanagedType.U1)]bool deleted, [MarshalAs(UnmanagedType.U1)]bool hasAttachments, C4Slice* history, 
     uint historyCount, C4Error *outError);
Example #23
0
 public static extern bool c4doc_setType(C4Document *doc, C4Slice docType, C4Error *outError);
Example #24
0
        // Must be called from within the ThreadSafety
        private void StartInternal()
        {
            _desc = ToString(); // Cache this; it may be called a lot when logging

            // Target:
            var      addr      = new C4Address();
            var      scheme    = new C4String();
            var      host      = new C4String();
            var      path      = new C4String();
            Database otherDB   = null;
            var      remoteUrl = Config.RemoteUrl;
            string   dbNameStr = null;

            if (remoteUrl != null)
            {
                var pathStr = String.Concat(remoteUrl.Segments.Take(remoteUrl.Segments.Length - 1));
                dbNameStr     = remoteUrl.Segments.Last().TrimEnd('/');
                scheme        = new C4String(remoteUrl.Scheme);
                host          = new C4String(remoteUrl.Host);
                path          = new C4String(pathStr);
                addr.scheme   = scheme.AsC4Slice();
                addr.hostname = host.AsC4Slice();
                addr.port     = (ushort)remoteUrl.Port;
                addr.path     = path.AsC4Slice();
            }
            else
            {
                otherDB = Config.OtherDB;
            }

            var options  = Config.Options;
            var userInfo = remoteUrl?.UserInfo?.Split(':');

            if (userInfo?.Length == 2)
            {
                throw new ArgumentException(
                          "Embedded credentials in a URL (username:password@url) are not allowed; use the BasicAuthenticator class instead");
            }

            Config.Authenticator?.Authenticate(options);

            options.Build();
            var push       = Config.ReplicatorType.HasFlag(ReplicatorType.Push);
            var pull       = Config.ReplicatorType.HasFlag(ReplicatorType.Pull);
            var continuous = Config.Continuous;

            // Clear the reset flag, it is a one-time thing
            Config.Options.Reset = false;

            var socketFactory = Config.SocketFactory;

            socketFactory.context = GCHandle.ToIntPtr(GCHandle.Alloc(this)).ToPointer();
            _nativeParams         = new ReplicatorParameters(options)
            {
                Push            = Mkmode(push, continuous),
                Pull            = Mkmode(pull, continuous),
                Context         = this,
                OnDocumentError = OnDocError,
                OnStatusChanged = StatusChangedCallback,
                SocketFactory   = &socketFactory
            };

            var err    = new C4Error();
            var status = default(C4ReplicatorStatus);

            _stopping = false;
            _databaseThreadSafety.DoLocked(() =>
            {
                C4Error localErr;
                _repl = Native.c4repl_new(Config.Database.c4db, addr, dbNameStr, otherDB != null ? otherDB.c4db : null,
                                          _nativeParams.C4Params, &localErr);
                err = localErr;
                if (_repl != null)
                {
                    status = Native.c4repl_getStatus(_repl);
                    Config.Database.ActiveReplications.Add(this);
                }
                else
                {
                    status = new C4ReplicatorStatus {
                        error    = err,
                        level    = C4ReplicatorActivityLevel.Stopped,
                        progress = new C4Progress()
                    };
                }
            });

            scheme.Dispose();
            path.Dispose();
            host.Dispose();

            UpdateStateProperties(status);
            DispatchQueue.DispatchSync(() => StatusChangedCallback(status));
        }
Example #25
0
 private static extern C4Document* _c4doc_get(C4Database *db, C4Slice docID, [MarshalAs(UnmanagedType.U1)]bool mustExist, 
     C4Error *outError);
Example #26
0
 public static extern bool c4db_purgeDoc(C4Database *db, C4Slice docId, C4Error *outError);
Example #27
0
 public static bool c4db_purgeDoc(C4Database *db, string docId, C4Error *outError)
 {
     using (var docId_ = new C4String(docId)) {
         return c4db_purgeDoc(db, docId_.AsC4Slice(), outError);
     }
 }
Example #28
0
 public static extern C4Document* c4doc_getBySequence(C4Database *db, ulong sequence, C4Error *outError);
Example #29
0
 /// <summary>
 /// Gets a document from the database. If there's no such document, the behavior depends on
 /// the mustExist flag.If it's true, NULL is returned. If it's false, a valid C4Document
 /// The current revision is selected(if the document exists.)
 /// </summary>
 /// <param name="db">The database to retrieve from</param>
 /// <param name="docID">The ID of the document to retrieve</param>
 /// <param name="mustExist">Whether or not to create the document on demand</param>
 /// <param name="outError">The error that occurred if the operation doesn't succeed</param>
 /// <returns>A pointer to the retrieved document on success, or null on failure</returns>
 public static C4Document* c4doc_get(C4Database *db, string docID, bool mustExist, C4Error *outError)
 {
     using(var docID_ = new C4String(docID)) {
         return c4doc_get(db, docID_.AsC4Slice(), mustExist, outError);   
     }
 }
Example #30
0
        public static C4Document* c4doc_get(C4Database *db, C4Slice docID, bool mustExist, 
            C4Error *outError)
        {
            #if DEBUG
            var retVal = _c4doc_get(db, docID, mustExist, outError);
            if(retVal != null) {
                _AllocatedObjects[(IntPtr)retVal] = "C4Document";
                #if ENABLE_LOGGING
                Console.WriteLine("[c4doc_get] Allocated 0x{0}", ((IntPtr)retVal).ToString("X"));
                #endif
            }

            return retVal;
            #else
            return _c4doc_get(db, docID, mustExist, outError);
            #endif
        }
 internal CouchbaseNetworkException(C4Error err) : base(err)
 {
 }
 internal CouchbaseWebsocketException(C4Error err) : base(err)
 {
 }
Example #33
0
 public static extern bool c4doc_selectRevision(C4Document *doc, C4Slice revID, [MarshalAs(UnmanagedType.U1)]bool withBody, 
     C4Error *outError);
 internal CouchbasePosixException(C4Error err) : base(err)
 {
 }
Example #35
0
        public void TestCRUD()
        {
            RunTestVariants(() =>
            {
                if (!IsRevTrees())
                {
                    return;
                }

                var body        = "{\"foo\":1, \"bar\":false}";
                var updatedBody = "{\"foo\":1, \"bar\":false, \"status\": \"updated!\"}";

                // TODO: Observer

                C4Error error;
                var doc = Native.c4doc_get(Db, "nonexistent", true, &error);
                ((IntPtr)doc).Should().Be(IntPtr.Zero, "because it does not exist");
                error.domain.Should().Be(C4ErrorDomain.LiteCoreDomain);
                error.code.Should().Be((int)C4ErrorCode.NotFound);

                // KeepBody => Revision's body should not be discarded when non-leaf
                doc = PutDoc(null, null, body, C4RevisionFlags.KeepBody);
                doc->docID.size.Should().BeGreaterOrEqualTo(10, "because otherwise no docID was created");

                var docID  = doc->docID.CreateString();
                var revID1 = doc->revID.CreateString();
                revID1.Should().StartWith("1-", "because otherwise the generation is invalid");
                Native.c4doc_free(doc);

                doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, docID, true, err));
                doc->docID.CreateString().Should().Be(docID);
                doc->selectedRev.revID.CreateString().Should().Be(revID1);
                doc->selectedRev.body.CreateString().Should().Be(body);
                Native.c4doc_free(doc);

                doc = PutDoc(docID, revID1, updatedBody, C4RevisionFlags.KeepBody);
                doc->docID.CreateString().Should().Be(docID);
                doc->selectedRev.body.CreateString().Should().Be(updatedBody);
                var revID2 = doc->revID.CreateString();
                revID2.Should().StartWith("2-", "because otherwise the generation is invalid");
                Native.c4doc_free(doc);

                error = new C4Error(C4ErrorCode.Conflict);
                PutDocMustFail(docID, revID1, updatedBody, C4RevisionFlags.KeepBody, error);

                var e = (C4DocEnumerator *)LiteCoreBridge.Check(err =>
                {
                    var options = C4EnumeratorOptions.Default;
                    return(Native.c4db_enumerateChanges(Db, 0, &options, err));
                });

                var seq = 2UL;
                while (null != (doc = c4enum_nextDocument(e, &error)))
                {
                    doc->selectedRev.sequence.Should().Be(seq);
                    doc->selectedRev.revID.CreateString().Should().Be(revID2);
                    doc->docID.CreateString().Should().Be(docID);
                    Native.c4doc_free(doc);
                    seq++;
                }

                seq.Should().Be(3UL);
                Native.c4enum_free(e);

                // NOTE: Filter is out of LiteCore scope

                error = new C4Error(C4ErrorCode.InvalidParameter);
                PutDocMustFail(docID, null, null, C4RevisionFlags.Deleted, error);

                doc = PutDoc(docID, revID2, null, C4RevisionFlags.Deleted);
                doc->flags.Should().Be(C4DocumentFlags.DocExists | C4DocumentFlags.DocDeleted);
                doc->docID.CreateString().Should().Be(docID);
                var revID3 = doc->revID.CreateString();
                revID3.Should().StartWith("3-", "because otherwise the generation is invalid");
                Native.c4doc_free(doc);

                doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, docID, true, err));
                doc->docID.CreateString().Should().Be(docID);
                doc->revID.CreateString().Should().Be(revID3);
                doc->flags.Should().Be(C4DocumentFlags.DocExists | C4DocumentFlags.DocDeleted);
                doc->selectedRev.revID.CreateString().Should().Be(revID3);
                doc->selectedRev.body.CreateString().Should().NotBeNull("because a valid revision should have a valid body");
                doc->selectedRev.flags.Should().Be(C4RevisionFlags.Leaf | C4RevisionFlags.Deleted);
                Native.c4doc_free(doc);

                PutDocMustFail("fake", null, null, C4RevisionFlags.Deleted, error);

                e = (C4DocEnumerator *)LiteCoreBridge.Check(err =>
                {
                    var options = C4EnumeratorOptions.Default;
                    return(Native.c4db_enumerateChanges(Db, 0, &options, err));
                });

                seq = 3UL;
                while (null != (doc = c4enum_nextDocument(e, &error)))
                {
                    Native.c4doc_free(doc);
                    seq++;
                }

                seq.Should().Be(3UL, "because deleted documents were not included");
                Native.c4enum_free(e);

                e = (C4DocEnumerator *)LiteCoreBridge.Check(err =>
                {
                    var options    = C4EnumeratorOptions.Default;
                    options.flags |= C4EnumeratorFlags.IncludeDeleted;
                    return(Native.c4db_enumerateChanges(Db, 0, &options, err));
                });

                seq = 3UL;
                while (null != (doc = c4enum_nextDocument(e, &error)))
                {
                    doc->selectedRev.sequence.Should().Be(seq);
                    doc->selectedRev.revID.CreateString().Should().Be(revID3);
                    doc->docID.CreateString().Should().Be(docID);
                    Native.c4doc_free(doc);
                    seq++;
                }

                seq.Should().Be(4UL, "because deleted documents were included");
                Native.c4enum_free(e);

                doc        = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, docID, true, err));
                var latest = 3;
                do
                {
                    switch (latest)
                    {
                    case 3:
                        doc->selectedRev.revID.CreateString().Should().Be(revID3);
                        break;

                    case 2:
                        doc->selectedRev.revID.CreateString().Should().Be(revID2);
                        break;

                    case 1:
                        doc->selectedRev.revID.CreateString().Should().Be(revID1);
                        break;

                    default:
                        throw new InvalidOperationException("Invalid switch portion reached");
                    }

                    latest--;
                } while (Native.c4doc_selectParentRevision(doc));

                latest.Should().Be(0, "because otherwise the history is not valid");
                Native.c4doc_free(doc);

                doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, docID, true, err));
                LiteCoreBridge.Check(err => Native.c4doc_selectRevision(doc, revID2, true, err));
                doc->selectedRev.revID.CreateString().Should().Be(revID2);
                doc->selectedRev.body.CreateString().Should().Be(updatedBody);
                Native.c4doc_free(doc);

                LiteCoreBridge.Check(err => Native.c4db_compact(Db, err));
                doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, docID, true, err));
                LiteCoreBridge.Check(err => Native.c4doc_selectRevision(doc, revID2, true, err));
                doc->selectedRev.revID.CreateString().Should().Be(revID2);
                // doc->selectedRev.body.CreateString().Should().BeNull("because the database was compacted");
                Native.c4doc_free(doc);

                // Check history again after compaction
                doc    = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, docID, true, err));
                latest = 3;
                do
                {
                    switch (latest)
                    {
                    case 3:
                        doc->selectedRev.revID.CreateString().Should().Be(revID3);
                        break;

                    case 2:
                        doc->selectedRev.revID.CreateString().Should().Be(revID2);
                        break;

                    case 1:
                        doc->selectedRev.revID.CreateString().Should().Be(revID1);
                        break;

                    default:
                        throw new InvalidOperationException("Invalid switch portion reached");
                    }

                    latest--;
                } while (Native.c4doc_selectParentRevision(doc));

                latest.Should().Be(0, "because otherwise the history is not valid");
                Native.c4doc_free(doc);
            });
        }
Example #36
0
 /// <summary>
 /// Selects a specific revision of a document (or no revision, if revID is NULL.)
 /// </summary>
 /// <param name="doc">The document to operate on</param>
 /// <param name="revID">The revID of the revision to select</param>
 /// <param name="withBody">Whether or not to load the body of the revision</param>
 /// <param name="outError">The error that occurred if the operation doesn't succeed</param>
 /// <returns>true on success, false otherwise</returns>
 public static bool c4doc_selectRevision(C4Document *doc, string revID, bool withBody, C4Error *outError)
 {
     using(var revID_ = new C4String(revID)) {
         return c4doc_selectRevision(doc, revID_.AsC4Slice(), withBody, outError);
     }
 }
Example #37
0
 /// <summary>
 /// Adds a revision to a document, as a child of the currently selected revision
 /// (or as a root revision if there is no selected revision.)
 /// On success, the new revision will be selected.
 /// Must be called within a transaction.
 /// </summary>
 /// <param name="doc">The document to operate on</param>
 /// <param name="revID">The ID of the revision being inserted</param>
 /// <param name="body">The (JSON) body of the revision</param>
 /// <param name="deleted">True if this revision is a deletion (tombstone)</param>
 /// <param name="hasAttachments">True if this revision contains an _attachments dictionary</param>
 /// <param name="allowConflict">If false, and the parent is not a leaf, a 409 error is returned</param>
 /// <param name="outError">The error that occurred if the operation doesn't succeed</param>
 /// <returns>The number of revisions inserted (0, 1, or -1 on error)</returns>
 public static int c4doc_insertRevision(C4Document *doc, string revID, string body, bool deleted, bool hasAttachments,
     bool allowConflict, C4Error *outError)
 {
     using(var revID_ = new C4String(revID))
     using(var body_ = new C4String(body)) {
         return c4doc_insertRevision(doc, revID_.AsC4Slice(), body_.AsC4Slice(), deleted, 
             hasAttachments, allowConflict, outError);
     }
 }
Example #38
0
 public static extern bool c4doc_loadRevisionBody(C4Document *doc, C4Error *outError);
Example #39
0
 /// <summary>
 ///  Adds a revision to a document, plus its ancestors (given in reverse chronological order.)
 /// On success, the new revision will be selected.
 /// Must be called within a transaction.
 /// <param name="doc">The document to operate on</param>
 /// <param name="body">The (JSON) body of the revision</param>
 /// <param name="deleted">True if this revision is a deletion (tombstone)</param>
 /// <param name="hasAttachments">True if this revision contains an _attachments dictionary</param>
 /// <param name="history">The ancestors' revision IDs, starting with the parent, in reverse order</param>
 /// <param name="outError">The error that occurred if the operation doesn't succeed</param>
 /// <returns>The number of revisions added to the document, or -1 on error.</returns>
 public static int c4doc_insertRevisionWithHistory(C4Document *doc, string body, bool deleted, 
     bool hasAttachments, string[] history, C4Error *outError)
 {
     var flattenedStringArray = new C4String[history.Length + 1];
     flattenedStringArray[0] = new C4String(body);
     for(int i = 0; i < history.Length; i++) {
         flattenedStringArray[i + 1] = new C4String(history[i]);
     }
     
     var sliceArray = flattenedStringArray.Skip(1).Select<C4String, C4Slice>(x => x.AsC4Slice()).ToArray(); 
     var retVal = default(int);
     fixed(C4Slice *a = sliceArray) {
         retVal = c4doc_insertRevisionWithHistory(doc, 
             flattenedStringArray[0].AsC4Slice(), deleted, hasAttachments, a, (uint)history.Length, outError);
     }
     
     foreach(var s in flattenedStringArray) {
         s.Dispose();   
     }
     
     return retVal;
 }
Example #40
0
 public static extern bool c4doc_selectNextLeafRevision(C4Document *doc, [MarshalAs(UnmanagedType.U1)]bool includeDeleted, 
     [MarshalAs(UnmanagedType.U1)]bool withBody, C4Error *outError);
Example #41
0
 public static extern int c4doc_purgeRevision(C4Document *doc, C4Slice revId, C4Error *outError);
Example #42
0
 private static extern C4DocEnumerator* _c4db_enumerateChanges(C4Database* db, ulong since, C4EnumeratorOptions* options, 
     C4Error* outError);
Example #43
0
 public static int c4doc_purgeRevision(C4Document *doc, string revId, C4Error *outError)
 {
     using(var revId_ = new C4String(revId)) {
         return c4doc_purgeRevision(doc, revId_.AsC4Slice(), outError);   
     }
 }
Example #44
0
 private static extern C4DocEnumerator* _c4db_enumerateAllDocs(C4Database *db, C4Slice startDocID, C4Slice endDocID, 
     C4EnumeratorOptions *options, C4Error *outError);
Example #45
0
 private static extern C4Database* _c4db_open(C4Slice path, C4DatabaseFlags flags, 
     C4EncryptionKey *encryptionKey, C4Error *outError);
Example #46
0
 public CBForestException(C4Error error)
     : base(String.Format("CBForest exception ({0})", error))
 {
     Error = error;
 }
 internal CouchbaseFleeceException(C4Error err) : base(err)
 {
 }
Example #48
0
 private static extern C4DocEnumerator* _c4db_enumerateSomeDocs(C4Database *db, C4Slice* docIDs, uint docIDsCount, 
     C4EnumeratorOptions *options, C4Error *outError);
 private static string VisitDefault(C4Error err) => $"CouchbaseLiteException ({err.domain} / {err.code}): {Native.c4error_getMessage(err)}.";
Example #50
0
        public static C4DocEnumerator* c4db_enumerateSomeDocs(C4Database *db, string[] docIDs, C4EnumeratorOptions *options,
            C4Error *outError)
        {
            var c4StringArr = docIDs.Select(x => new C4String(x)).ToArray();
            var sliceArr = c4StringArr.Select(x => x.AsC4Slice()).ToArray();
            var retVal = default(C4DocEnumerator*);
            fixed(C4Slice* ptr = sliceArr) {
                retVal = _c4db_enumerateSomeDocs(db, ptr, (uint)docIDs.Length, options, outError);
                #if DEBUG
                if(retVal != null) {
                    _AllocatedObjects[(IntPtr)retVal] = "C4DocEnumerator";
                #if ENABLE_LOGGING
                    Console.WriteLine("[c4db_enumerateSomeDocs] Allocated 0x{0}", ((IntPtr)retVal).ToString("X"));
                #endif
                }
                #endif
            }

            foreach (var c4str in c4StringArr) {
                c4str.Dispose();
            }

            return retVal;
        }
Example #51
0
        private static void OnDocError(C4Replicator *repl, bool pushing, C4Slice docID, C4Error error, bool transient, void *context)
        {
            var replicator = GCHandle.FromIntPtr((IntPtr)context).Target as Replicator;

            replicator?.DispatchQueue.DispatchAsync(() =>
            {
                replicator.OnDocError(error, pushing, docID.CreateString() ?? "", transient);
            });
        }
Example #52
0
 private static extern C4Document* _c4enum_nextDocument(C4DocEnumerator *e, C4Error *outError);
 internal CouchbaseException(C4Error err, string message, Exception innerException) : base(message, innerException)
 {
     LiteCoreError = err;
     Error         = MapError(err);
     Domain        = MapDomain(err);
 }
Example #54
0
        /// <summary>
        /// Returns the next document from an enumerator, or NULL if there are no more.
        /// The caller is responsible for freeing the C4Document.
        /// Don't forget to free the enumerator itself when finished with it.
        /// </summary>
        /// <param name="e">The enumerator to operate on</param>
        /// <param name="outError">The error that occurred if the operation doesn't succeed</param>
        /// <returns>A pointer to the document on success, otherwise null</returns>
        public static C4Document* c4enum_nextDocument(C4DocEnumerator *e, C4Error *outError)
        {
            #if DEBUG
            var retVal = _c4enum_nextDocument(e, outError);
            if(retVal != null) {
                _AllocatedObjects[(IntPtr)retVal] = "C4Document";
                #if ENABLE_LOGGING
                Console.WriteLine("[c4enum_nextDocument] Allocated 0x{0}", ((IntPtr)retVal).ToString("X"));
                #endif
            }

            return retVal;
            #else
            return _c4enum_nextDocument(e, outError);
            #endif
        }
Example #55
0
        private void PutDocMustFail(C4Database *db, string docID, string revID, string body, C4RevisionFlags flags, C4Error expected)
        {
            C4Error error;
            var     doc = PutDoc(db, docID, revID, body, flags, &error);

            ((IntPtr)doc).Should().Be(IntPtr.Zero, "because the put operation was expected to fail");
            WriteLine($"Error: {Native.c4error_getMessage(error)}");
            error.domain.Should().Be(expected.domain);
            error.code.Should().Be(expected.code);
        }
Example #56
0
 public static extern int c4doc_insertRevision(C4Document *doc, C4Slice revID, C4Slice body, 
     [MarshalAs(UnmanagedType.U1)]bool deleted, [MarshalAs(UnmanagedType.U1)]bool hasAttachments,
     [MarshalAs(UnmanagedType.U1)]bool allowConflict, C4Error *outError);
Example #57
0
 private void PutDocMustFail(string docID, string revID, string body, C4RevisionFlags flags, C4Error expected)
 {
     PutDocMustFail(Db, docID, revID, body, flags, expected);
 }
 internal CouchbaseSQLiteException(C4Error err) : base(err)
 {
 }
Example #59
0
        public static C4DocEnumerator* c4db_enumerateAllDocs(C4Database *db, C4Slice startDocID, C4Slice endDocID, 
            C4EnumeratorOptions *options, C4Error *outError)
        {
            #if DEBUG
            var retVal = _c4db_enumerateAllDocs(db, startDocID, endDocID, options, outError);
            if(retVal != null) {
                _AllocatedObjects[(IntPtr)retVal] = "C4DocEnumerator";
                #if ENABLE_LOGGING
                Console.WriteLine("[c4db_enumerateAllDocs] Allocated 0x{0}", ((IntPtr)retVal).ToString("X"));
                #endif
            }

            return retVal;
            #else
            return _c4db_enumerateAllDocs(db, startDocID, endDocID, options, outError);
            #endif
        }
Example #60
0
 /// <summary>
 /// Creates an enumerator ordered by docID.
 /// Options have the same meanings as in Couchbase Lite.
 /// There's no 'limit' option; just stop enumerating when you're done.
 /// Caller is responsible for freeing the enumerator when finished with it.
 /// </summary>
 /// <param name="db">The database to operate on</param>
 /// <param name="startDocID">The document ID to begin at</param>
 /// <param name="endDocID">The document ID to end at</param>
 /// <param name="options">Enumeration options (NULL for defaults)</param>
 /// <param name="outError">The error that occurred if the operation doesn't succeed</param>
 /// <returns>A pointer to the enumeator on success, otherwise null</returns>
 public static C4DocEnumerator* c4db_enumerateAllDocs(C4Database *db, string startDocID, string endDocID, C4EnumeratorOptions *options,
     C4Error *outError)
 {
     using(var startDocID_ = new C4String(startDocID))
     using(var endDocID_ = new C4String(endDocID)) {
         return c4db_enumerateAllDocs(db, startDocID_.AsC4Slice(), endDocID_.AsC4Slice(), options, outError);
     }
 }