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; }); }
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)); } }
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); }
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); }
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); } }
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); } }
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); }
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); }
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 }
/// <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); } }
public static extern bool c4doc_save(C4Document *doc, uint maxRevTreeDepth, C4Error *outError);
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);
public static extern bool c4doc_setType(C4Document *doc, C4Slice docType, C4Error *outError);
// 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)); }
private static extern C4Document* _c4doc_get(C4Database *db, C4Slice docID, [MarshalAs(UnmanagedType.U1)]bool mustExist, C4Error *outError);
public static extern bool c4db_purgeDoc(C4Database *db, C4Slice docId, C4Error *outError);
public static bool c4db_purgeDoc(C4Database *db, string docId, C4Error *outError) { using (var docId_ = new C4String(docId)) { return c4db_purgeDoc(db, docId_.AsC4Slice(), outError); } }
public static extern C4Document* c4doc_getBySequence(C4Database *db, ulong sequence, C4Error *outError);
/// <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); } }
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) { }
public static extern bool c4doc_selectRevision(C4Document *doc, C4Slice revID, [MarshalAs(UnmanagedType.U1)]bool withBody, C4Error *outError);
internal CouchbasePosixException(C4Error err) : base(err) { }
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); }); }
/// <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); } }
/// <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); } }
public static extern bool c4doc_loadRevisionBody(C4Document *doc, C4Error *outError);
/// <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; }
public static extern bool c4doc_selectNextLeafRevision(C4Document *doc, [MarshalAs(UnmanagedType.U1)]bool includeDeleted, [MarshalAs(UnmanagedType.U1)]bool withBody, C4Error *outError);
public static extern int c4doc_purgeRevision(C4Document *doc, C4Slice revId, C4Error *outError);
private static extern C4DocEnumerator* _c4db_enumerateChanges(C4Database* db, ulong since, C4EnumeratorOptions* options, C4Error* outError);
public static int c4doc_purgeRevision(C4Document *doc, string revId, C4Error *outError) { using(var revId_ = new C4String(revId)) { return c4doc_purgeRevision(doc, revId_.AsC4Slice(), outError); } }
private static extern C4DocEnumerator* _c4db_enumerateAllDocs(C4Database *db, C4Slice startDocID, C4Slice endDocID, C4EnumeratorOptions *options, C4Error *outError);
private static extern C4Database* _c4db_open(C4Slice path, C4DatabaseFlags flags, C4EncryptionKey *encryptionKey, C4Error *outError);
public CBForestException(C4Error error) : base(String.Format("CBForest exception ({0})", error)) { Error = error; }
internal CouchbaseFleeceException(C4Error err) : base(err) { }
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)}.";
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; }
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); }); }
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); }
/// <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 }
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); }
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);
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) { }
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 }
/// <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); } }