/// <summary> /// Similar to the LINQ method, but returns all objects converted to standard /// .NET types /// </summary> /// <returns>A list of standard .NET typed objects in the array</returns> public List <object> ToList() { var count = _array.Count; var result = new List <object>(count); _threadSafety.DoLocked(() => { for (var i = 0; i < count; i++) { result.Add(DataOps.ToNetObject(GetObject(_array, i))); } }); return(result); }
public IList <string> GetIndexes() { object retVal = null; ThreadSafety.DoLocked(() => { CheckOpen(); var result = new C4SliceResult(); LiteCoreBridge.Check(err => { result = NativeRaw.c4db_getIndexes(c4db, err); return(result.buf != null); }); var val = NativeRaw.FLValue_FromTrustedData(new FLSlice(result.buf, result.size)); if (val == null) { Native.c4slice_free(result); throw new LiteCoreException(new C4Error(C4ErrorCode.CorruptIndexData)); } retVal = FLValueConverter.ToCouchbaseObject(val, this, true, typeof(string)); Native.c4slice_free(result); }); return(retVal as IList <string> ?? new List <string>()); }
public void InBatch(Action action) { CBDebug.MustNotBeNull(Log.To.Database, Tag, nameof(action), action); ThreadSafety.DoLocked(() => { CheckOpen(); PerfTimer.StartEvent("InBatch_BeginTransaction"); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(_c4db, err)); PerfTimer.StopEvent("InBatch_BeginTransaction"); var success = true; try { action(); } catch (Exception e) { Log.To.Database.W(Tag, "Exception during InBatch, rolling back...", e); success = false; throw; } finally { PerfTimer.StartEvent("InBatch_EndTransaction"); LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, success, err)); PerfTimer.StopEvent("InBatch_EndTransaction"); } }); PostDatabaseChanged(); }
public void Purge(Document document) { CBDebug.MustNotBeNull(Log.To.Database, Tag, nameof(document), document); ThreadSafety.DoLocked(() => { CheckOpen(); VerifyDB(document); if (!document.Exists) { var docID = new SecureLogString(document.Id, LogMessageSensitivity.PotentiallyInsecure); Log.To.Database.V(Tag, $"Ignoring purge of non-existent document {docID}"); return; } InBatch(() => { var result = Native.c4doc_purgeRevision(document.c4Doc.RawDoc, null, null); if (result >= 0) { LiteCoreBridge.Check(err => Native.c4doc_save(document.c4Doc.RawDoc, 0, err)); } }); }); }
/// <summary> /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// </summary> public void Dispose() { GC.SuppressFinalize(this); ThreadSafety.DoLocked(() => { ThrowIfActiveItems(); Dispose(true); }); }
/// <summary> /// Creates a database given a name and some configuration /// </summary> /// <param name="name">The name of the database</param> /// <param name="configuration">The configuration to open it with</param> public Database(string name, DatabaseConfiguration configuration = null) { Name = CBDebug.MustNotBeNull(Log.To.Database, Tag, nameof(name), name); Config = configuration?.Freeze() ?? new DatabaseConfiguration(true); Open(); FLSharedKeys *keys = null; ThreadSafety.DoLocked(() => keys = Native.c4db_getFLSharedKeys(_c4db)); _sharedStrings = new SharedStringCache(keys); }
public void Delete(Document document) { CBDebug.MustNotBeNull(Log.To.Database, Tag, nameof(document), document); ThreadSafety.DoLocked(() => { CheckOpen(); Save(document, true); }); }
public Document Save(MutableDocument document) { CBDebug.MustNotBeNull(Log.To.Database, Tag, nameof(document), document); return(ThreadSafety.DoLocked(() => { CheckOpen(); return Save(document, false); })); }
/// <summary> /// Deletes the database /// </summary> public void Delete() { ThreadSafety.DoLocked(() => { CheckOpen(); ThrowIfActiveItems(); LiteCoreBridge.Check(err => Native.c4db_delete(_c4db, err)); Native.c4db_free(_c4db); _c4db = null; _obs?.Dispose(); _obs = null; }); }
public ListenerToken AddChangeListener([CanBeNull] TaskScheduler scheduler, EventHandler <DatabaseChangedEventArgs> handler) { CBDebug.MustNotBeNull(Log.To.Database, Tag, nameof(handler), handler); return(ThreadSafety.DoLocked(() => { CheckOpen(); var cbHandler = new CouchbaseEventHandler <DatabaseChangedEventArgs>(handler, scheduler); _databaseChanged.Add(cbHandler); return new ListenerToken(cbHandler, "db"); })); }
/// <summary> /// Removes a database changed listener by token /// </summary> /// <param name="token">The token received from <see cref="AddChangeListener(TaskScheduler, EventHandler{DatabaseChangedEventArgs})"/> /// and family</param> public void RemoveChangeListener(ListenerToken token) { ThreadSafety.DoLocked(() => { CheckOpen(); if (token.Type == "db") { _databaseChanged.Remove(token); } else { _documentChanged.Remove(token); } }); }
private void PostDocChanged([NotNull] string documentID) { DocumentChangedEventArgs change = null; ThreadSafety.DoLocked(() => { if (!_docObs.ContainsKey(documentID) || _c4db == null || Native.c4db_isInTransaction(_c4db)) { return; } change = new DocumentChangedEventArgs(documentID, this); }); _documentChanged.Fire(documentID, this, change); }
private void Open() { if (_c4db != null) { return; } Directory.CreateDirectory(Config.Directory); var path = DatabasePath(Name, Config.Directory); var config = DBConfig; var encrypted = ""; #if COUCHBASE_ENTERPRISE if (Config.EncryptionKey != null) { var key = Config.EncryptionKey; var i = 0; config.encryptionKey.algorithm = C4EncryptionAlgorithm.AES256; foreach (var b in key.KeyData) { config.encryptionKey.bytes[i++] = b; } encrypted = "encrypted "; } #endif Log.To.Database.I(Tag, $"Opening {encrypted}database at {path}"); var localConfig1 = config; ThreadSafety.DoLocked(() => { _c4db = (C4Database *)NativeHandler.Create() .AllowError((int)C4ErrorCode.NotADatabaseFile, C4ErrorDomain.LiteCoreDomain).Execute(err => { var localConfig2 = localConfig1; return(Native.c4db_open(path, &localConfig2, err)); }); if (_c4db == null) { throw new CouchbaseLiteException(StatusCode.Unauthorized); } _obs = Native.c4dbobs_create(_c4db, _DbObserverCallback, this); }); }
private void PostDatabaseChanged() { var allChanges = new List <DatabaseChangedEventArgs>(); ThreadSafety.DoLocked(() => { if (_obs == null || _c4db == null || InTransaction) { return; } const uint maxChanges = 100u; var external = false; uint nChanges; var changes = new C4DatabaseChange[maxChanges]; var docIDs = new List <string>(); do { // Read changes in batches of MaxChanges: bool newExternal; nChanges = Native.c4dbobs_getChanges(_obs.Observer, changes, maxChanges, &newExternal); if (nChanges == 0 || external != newExternal || docIDs.Count > 1000) { if (docIDs.Count > 0) { // Only notify if there are actually changes to send var args = new DatabaseChangedEventArgs(this, docIDs); allChanges.Add(args); docIDs = new List <string>(); } } external = newExternal; for (var i = 0; i < nChanges; i++) { docIDs.Add(changes[i].docID.CreateString()); } } while (nChanges > 0); }); foreach (var args in allChanges) { _databaseChanged.Fire(this, args); } }
public void Save(Couchbase.Lite.Linq.IDocumentModel model) { CBDebug.MustNotBeNull(Log.To.Database, Tag, nameof(model), model); ThreadSafety.DoLocked(() => { CheckOpen(); MutableDocument md = (model.Document as MutableDocument) ?? model.Document?.ToMutable() ?? new MutableDocument(); md.SetFromModel(model); try { var retVal = Save(md, false); model.Document = retVal; } finally { md.Dispose(); } }); }
public IImmutableSet <string> GetPendingDocumentIDs() { var result = new HashSet <string>(); if (!IsPushing()) { CBDebug.LogAndThrow(WriteLog.To.Sync, new CouchbaseLiteException(C4ErrorCode.Unsupported, CouchbaseLiteErrorMessage.PullOnlyPendingDocIDs), Tag, CouchbaseLiteErrorMessage.PullOnlyPendingDocIDs, true); } DispatchQueue.DispatchSync(() => { var errSetupRepl = SetupC4Replicator(); if (errSetupRepl.code > 0) { CBDebug.LogAndThrow(WriteLog.To.Sync, CouchbaseException.Create(errSetupRepl), Tag, errSetupRepl.ToString(), true); } }); byte[] pendingDocIds = LiteCoreBridge.Check(err => { return(Native.c4repl_getPendingDocIDs(_repl, err)); }); if (pendingDocIds != null) { _databaseThreadSafety.DoLocked(() => { var flval = Native.FLValue_FromData(pendingDocIds, FLTrust.Trusted); var flarr = Native.FLValue_AsArray(flval); var cnt = (int)Native.FLArray_Count(flarr); for (int i = 0; i < cnt; i++) { var flv = Native.FLArray_Get(flarr, (uint)i); result.Add(Native.FLValue_AsString(flv)); } Array.Clear(pendingDocIds, 0, pendingDocIds.Length); pendingDocIds = null; }); } _pendingDocIds = result.ToImmutableHashSet <string>(); return(_pendingDocIds); }
public ListenerToken AddDocumentChangeListener(string id, [CanBeNull] TaskScheduler scheduler, EventHandler <DocumentChangedEventArgs> handler) { CBDebug.MustNotBeNull(Log.To.Database, Tag, nameof(id), id); CBDebug.MustNotBeNull(Log.To.Database, Tag, nameof(handler), handler); return(ThreadSafety.DoLocked(() => { CheckOpen(); var cbHandler = new CouchbaseEventHandler <string, DocumentChangedEventArgs>(handler, id, scheduler); var count = _documentChanged.Add(cbHandler); if (count == 0) { var docObs = new DocumentObserver(_c4db, id, _DocObserverCallback, this); _docObs[id] = docObs; } return new ListenerToken(cbHandler, "doc"); })); }
public void CreateIndex(string name, IIndex index) { CBDebug.MustNotBeNull(Log.To.Database, Tag, nameof(name), name); CBDebug.MustNotBeNull(Log.To.Database, Tag, nameof(index), index); ThreadSafety.DoLocked(() => { CheckOpen(); var concreteIndex = Misc.TryCast <IIndex, QueryIndex>(index); var jsonObj = concreteIndex.ToJSON(); var json = JsonConvert.SerializeObject(jsonObj); LiteCoreBridge.Check(err => { var internalOpts = concreteIndex.Options; // For some reason a "using" statement here causes a compiler error try { return(Native.c4db_createIndex(c4db, name, json, concreteIndex.IndexType, &internalOpts, err)); } finally { internalOpts.Dispose(); } }); }); }
// 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)); }
internal void ResolveConflict([NotNull] string docID, [NotNull] IConflictResolver resolver) { Debug.Assert(docID != null); Document doc = null, otherDoc = null, baseDoc = null; var inConflict = true; while (inConflict) { ThreadSafety.DoLocked(() => { LiteCoreBridge.Check(err => Native.c4db_beginTransaction(_c4db, err)); try { doc = new Document(this, docID); if (!doc.Exists) { doc.Dispose(); return; } otherDoc = new Document(this, docID); if (!otherDoc.Exists) { doc.Dispose(); otherDoc.Dispose(); return; } otherDoc.SelectConflictingRevision(); baseDoc = new Document(this, docID); if (!baseDoc.SelectCommonAncestor(doc, otherDoc) || baseDoc.ToDictionary() == null) { baseDoc.Dispose(); baseDoc = null; } LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, true, err)); } catch (Exception) { doc?.Dispose(); otherDoc?.Dispose(); baseDoc?.Dispose(); LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, false, err)); } }); var conflict = new Conflict(doc, otherDoc, baseDoc); var logID = new SecureLogString(doc.Id, LogMessageSensitivity.PotentiallyInsecure); Log.To.Database.I(Tag, $"Resolving doc '{logID}' with {resolver.GetType().Name} (mine={doc.RevID}, theirs={otherDoc.RevID}, base={baseDoc?.RevID})"); Document resolved = null; try { resolved = resolver.Resolve(conflict); if (resolved == null) { throw new LiteCoreException(new C4Error(C4ErrorCode.Conflict)); } SaveResolvedDocument(resolved, conflict); inConflict = false; } catch (LiteCoreException e) { if (e.Error.domain == C4ErrorDomain.LiteCoreDomain && e.Error.code == (int)C4ErrorCode.Conflict) { continue; } throw; } finally { resolved?.Dispose(); if (resolved != doc) { doc?.Dispose(); } if (resolved != otherDoc) { otherDoc?.Dispose(); } if (resolved != baseDoc) { baseDoc?.Dispose(); } } } }
private Document Save([NotNull] Document document, bool deletion) { if (document.IsInvalidated) { throw new CouchbaseLiteException(StatusCode.NotAllowed, "Cannot save or delete a MutableDocument that has already been used to save or delete"); } if (deletion && document.RevID == null) { throw new CouchbaseLiteException(StatusCode.NotAllowed, "Cannot delete a document that has not yet been saved"); } var docID = document.Id; var doc = document; Document baseDoc = null, otherDoc = null; C4Document *newDoc = null; Document retVal = null; while (true) { var resolve = false; retVal = ThreadSafety.DoLocked(() => { VerifyDB(doc); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(_c4db, err)); try { if (deletion) { // Check for no-op case if the document does not exist var curDoc = (C4Document *)NativeHandler.Create().AllowError(new C4Error(C4ErrorCode.NotFound)) .Execute(err => Native.c4doc_get(_c4db, docID, true, err)); if (curDoc == null) { (document as MutableDocument)?.MarkAsInvalidated(); return(null); } Native.c4doc_free(curDoc); } var newDocOther = newDoc; Save(doc, &newDocOther, baseDoc?.c4Doc?.HasValue == true ? baseDoc.c4Doc.RawDoc : null, deletion); if (newDocOther != null) { // Save succeeded, so commit newDoc = newDocOther; LiteCoreBridge.Check(err => { var success = Native.c4db_endTransaction(_c4db, true, err); if (!success) { Native.c4doc_free(newDoc); } return(success); }); (document as MutableDocument)?.MarkAsInvalidated(); baseDoc?.Dispose(); return(new Document(this, docID, new C4DocumentWrapper(newDoc))); } // There was a conflict if (deletion && !doc.IsDeleted) { var deletedDoc = doc.ToMutable(); deletedDoc.MarkAsDeleted(); doc = deletedDoc; } if (doc.c4Doc != null) { baseDoc = new Document(this, docID, doc.c4Doc.Retain <C4DocumentWrapper>()); } otherDoc = new Document(this, docID); if (!otherDoc.Exists) { LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, false, err)); return(null); } } catch (Exception) { baseDoc?.Dispose(); otherDoc?.Dispose(); LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, false, err)); throw; } resolve = true; LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, false, err)); return(null); }); if (!resolve) { return(retVal); } // Resolve Conflict Document resolved = null; try { var resolver = Config.ConflictResolver; var conflict = new Conflict(doc, otherDoc, baseDoc); resolved = resolver.Resolve(conflict); if (resolved == null) { throw new LiteCoreException(new C4Error(C4ErrorCode.Conflict)); } } finally { baseDoc?.Dispose(); if (!ReferenceEquals(resolved, otherDoc)) { otherDoc?.Dispose(); } } retVal = ThreadSafety.DoLocked(() => { var current = new Document(this, docID); if (resolved.RevID == current.RevID) { (document as MutableDocument)?.MarkAsInvalidated(); current.Dispose(); return(resolved); // Same as current } // For saving doc = resolved; baseDoc = current; deletion = resolved.IsDeleted; return(null); }); if (retVal != null) { return(retVal); } } }
private void Save([NotNull] Document doc, C4Document **outDoc, C4Document *baseDoc, bool deletion) { var revFlags = (C4RevisionFlags)0; if (deletion) { revFlags = C4RevisionFlags.Deleted; } byte[] body = null; if (!deletion && !doc.IsEmpty) { body = doc.Encode(); var root = Native.FLValue_FromTrustedData(body); if (root == null) { Log.To.Database.E(Tag, "Failed to encode document body properly. Aborting save of document!"); return; } var rootDict = Native.FLValue_AsDict(root); if (rootDict == null) { Log.To.Database.E(Tag, "Failed to encode document body properly. Aborting save of document!"); return; } ThreadSafety.DoLocked(() => { if (Native.c4doc_dictContainsBlobs(rootDict, SharedStrings.SharedKeys)) { revFlags |= C4RevisionFlags.HasAttachments; } }); } else if (doc.IsEmpty) { FLEncoder *encoder = SharedEncoder; Native.FLEncoder_BeginDict(encoder, 0); Native.FLEncoder_EndDict(encoder); body = Native.FLEncoder_Finish(encoder, null); Native.FLEncoder_Reset(encoder); } var rawDoc = baseDoc != null ? baseDoc : doc.c4Doc?.HasValue == true ? doc.c4Doc.RawDoc : null; if (rawDoc != null) { doc.ThreadSafety.DoLocked(() => { ThreadSafety.DoLocked(() => { *outDoc = (C4Document *)NativeHandler.Create() .AllowError((int)C4ErrorCode.Conflict, C4ErrorDomain.LiteCoreDomain).Execute( err => Native.c4doc_update(rawDoc, body, revFlags, err)); }); }); } else { ThreadSafety.DoLocked(() => { *outDoc = (C4Document *)NativeHandler.Create() .AllowError((int)C4ErrorCode.Conflict, C4ErrorDomain.LiteCoreDomain).Execute( err => Native.c4doc_create(_c4db, doc.Id, body, revFlags, err)); }); } }
public Document GetDocument(string id) { CBDebug.MustNotBeNull(Log.To.Database, Tag, nameof(id), id); return(ThreadSafety.DoLocked(() => GetDocumentInternal(id))); }