コード例 #1
0
        public void TestForceInsertEmptyHistory()
        {
            var rev = new RevisionInternal("FakeDocId", "1-abcd".AsRevID(), false);
            var revProperties = new Dictionary<string, object>();
            revProperties.SetDocRevID(rev.DocID, rev.RevID);
            revProperties["message"] = "hi";
            rev.SetProperties(revProperties);

            IList<RevisionID> revHistory = null;
            database.ForceInsert(rev, revHistory, null);
        }
コード例 #2
0
        public void TestForceInsertEmptyHistory()
        {
            var rev = new RevisionInternal("FakeDocId", "1-abcd", false);
            var revProperties = new Dictionary<string, object>();
            revProperties.Put("_id", rev.GetDocId());
            revProperties.Put("_rev", rev.GetRevId());
            revProperties["message"] = "hi";
            rev.SetProperties(revProperties);

            IList<string> revHistory = null;
            database.ForceInsert(rev, revHistory, null);
        }
コード例 #3
0
		/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
		public virtual void TestForceInsertEmptyHistory()
		{
			IList<string> revHistory = null;
			RevisionInternal rev = new RevisionInternal("FakeDocId", "1-tango", false, database
				);
			IDictionary<string, object> revProperties = new Dictionary<string, object>();
			revProperties.Put("_id", rev.GetDocId());
			revProperties.Put("_rev", rev.GetRevId());
			revProperties.Put("message", "hi");
			rev.SetProperties(revProperties);
			database.ForceInsert(rev, revHistory, null);
		}
コード例 #4
0
        public RevisionInternal Copy(string docId, RevisionID revId)
        {
            System.Diagnostics.Debug.Assert((docId != null));
            System.Diagnostics.Debug.Assert(((_docId == null) || (_docId.Equals(docId))));

            var result = new RevisionInternal(docId, revId, Deleted);
            var unmodifiableProperties = GetProperties();
            var properties             = new Dictionary <string, object>();

            if (unmodifiableProperties != null)
            {
                properties.PutAll(unmodifiableProperties);
            }

            properties.SetDocRevID(docId, revId);
            result.SetProperties(properties);
            return(result);
        }
コード例 #5
0
        internal RevisionInternal CopyWithDocID(String docId, String revId)
        {
            System.Diagnostics.Debug.Assert((docId != null));
            System.Diagnostics.Debug.Assert(((this._docId == null) || (this._docId.Equals(docId))));

            var result = new RevisionInternal(docId, revId, _deleted);
            var unmodifiableProperties = GetProperties();
            var properties             = new Dictionary <string, object>();

            if (unmodifiableProperties != null)
            {
                properties.PutAll(unmodifiableProperties);
            }
            properties["_id"]  = docId;
            properties["_rev"] = revId;
            result.SetProperties(properties);
            return(result);
        }
コード例 #6
0
        public virtual Couchbase.Lite.Internal.RevisionInternal CopyWithDocID(string docId
                                                                              , string revId)
        {
            System.Diagnostics.Debug.Assert(((docId != null) && (revId != null)));
            System.Diagnostics.Debug.Assert(((this.docId == null) || (this.docId.Equals(docId
                                                                                        ))));
            Couchbase.Lite.Internal.RevisionInternal result = new Couchbase.Lite.Internal.RevisionInternal
                                                                  (docId, revId, deleted, database);
            IDictionary <string, object> unmodifiableProperties = GetProperties();
            IDictionary <string, object> properties             = new Dictionary <string, object>();

            if (unmodifiableProperties != null)
            {
                properties.PutAll(unmodifiableProperties);
            }
            properties.Put("_id", docId);
            properties.Put("_rev", revId);
            result.SetProperties(properties);
            return(result);
        }
コード例 #7
0
ファイル: Database.cs プロジェクト: Redth/couchbase-lite-net
		public RevisionInternal GetLocalDocument(string docID, string revID)
		{
			// docID already should contain "_local/" prefix
			RevisionInternal result = null;
			Cursor cursor = null;
			try
			{
				string[] args = new string[] { docID };
				cursor = database.RawQuery("SELECT revid, json FROM localdocs WHERE docid=?", args
					);
				if (cursor.MoveToNext())
				{
					string gotRevID = cursor.GetString(0);
					if (revID != null && (!revID.Equals(gotRevID)))
					{
						return null;
					}
					byte[] json = cursor.GetBlob(1);
					IDictionary<string, object> properties = null;
					try
					{
						properties = Manager.GetObjectMapper().ReadValue<IDictionary>(json);
						properties.Put("_id", docID);
						properties.Put("_rev", gotRevID);
						result = new RevisionInternal(docID, gotRevID, false, this);
						result.SetProperties(properties);
					}
					catch (Exception e)
					{
						Log.W(Database.Tag, "Error parsing local doc JSON", e);
						return null;
					}
				}
				return result;
			}
			catch (SQLException e)
			{
				Log.E(Database.Tag, "Error getting local document", e);
				return null;
			}
			finally
			{
				if (cursor != null)
				{
					cursor.Close();
				}
			}
		}
コード例 #8
0
ファイル: Database.cs プロジェクト: Redth/couchbase-lite-net
		public void StubOutAttachmentsIn(RevisionInternal rev, int minRevPos)
		{
			if (minRevPos <= 1)
			{
				return;
			}
			IDictionary<string, object> properties = (IDictionary<string, object>)rev.GetProperties
				();
			IDictionary<string, object> attachments = null;
			if (properties != null)
			{
				attachments = (IDictionary<string, object>)properties.Get("_attachments");
			}
			IDictionary<string, object> editedProperties = null;
			IDictionary<string, object> editedAttachments = null;
			foreach (string name in attachments.Keys)
			{
				IDictionary<string, object> attachment = (IDictionary<string, object>)attachments
					.Get(name);
				int revPos = (int)attachment.Get("revpos");
				object stub = attachment.Get("stub");
				if (revPos > 0 && revPos < minRevPos && (stub == null))
				{
					// Strip this attachment's body. First make its dictionary mutable:
					if (editedProperties == null)
					{
						editedProperties = new Dictionary<string, object>(properties);
						editedAttachments = new Dictionary<string, object>(attachments);
						editedProperties.Put("_attachments", editedAttachments);
					}
					// ...then remove the 'data' and 'follows' key:
					IDictionary<string, object> editedAttachment = new Dictionary<string, object>(attachment
						);
					Sharpen.Collections.Remove(editedAttachment, "data");
					Sharpen.Collections.Remove(editedAttachment, "follows");
					editedAttachment.Put("stub", true);
					editedAttachments.Put(name, editedAttachment);
					Log.D(Database.Tag, "Stubbed out attachment" + rev + " " + name + ": revpos" + revPos
						 + " " + minRevPos);
				}
			}
			if (editedProperties != null)
			{
				rev.SetProperties(editedProperties);
			}
		}
コード例 #9
0
 /// <summary>
 /// Sets the contents of the local <see cref="Couchbase.Lite.Document" /> with the given id.  If <param name="properties"/> is null, the 
 /// <see cref="Couchbase.Lite.Document" /> is deleted.
 /// </summary>
 /// <param name="id">The id of the local document whos contents to set.</param>
 /// <param name="properties">The contents to set for the local document.</param>
 /// <exception cref="Couchbase.Lite.CouchbaseLiteException">Thrown if an issue occurs 
 /// while setting the contents of the local document.</exception>
 public bool PutLocalDocument(string id, IDictionary<string, object> properties) 
 { 
     id = MakeLocalDocumentId(id);
     var rev = new RevisionInternal(id, null, properties == null);
     if (properties != null) {
         rev.SetProperties(properties);
     }
         
     bool ok = Storage.PutLocalRevision(rev, null, false) != null;
     return ok;
 }
コード例 #10
0
        private void SetupRevisionBodyTransformationFunction()
        {
            var xformer = TransformationFunction;
            if (xformer != null)
            {
                RevisionBodyTransformationFunction = (rev) =>
                {
                    var properties = rev.GetProperties();

                    var xformedProperties = xformer(properties);
                    if (xformedProperties == null) 
                    {
                        return null;
                    }
                    if (xformedProperties != properties) {
                        Debug.Assert (xformedProperties != null);
                        Debug.Assert (xformedProperties ["_id"].Equals (properties ["_id"]));
                        Debug.Assert (xformedProperties ["_rev"].Equals (properties ["_rev"]));

                        var nuRev = new RevisionInternal (rev.GetProperties ());
                        nuRev.SetProperties (xformedProperties);
                        return nuRev;
                    }
                    return rev;
                };
            }
        }
コード例 #11
0
        /// <summary>Updates or deletes an attachment, creating a new document revision in the process.
        ///     </summary>
        /// <remarks>
        /// Updates or deletes an attachment, creating a new document revision in the process.
        /// Used by the PUT / DELETE methods called on attachment URLs.
        /// </remarks>
        /// <exclude></exclude>
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
        internal RevisionInternal UpdateAttachment(string filename, BlobStoreWriter body, string contentType, AttachmentEncoding encoding, string docID, string oldRevID)
        {
            var isSuccessful = false;
            if (String.IsNullOrEmpty (filename) || (body != null && contentType == null) || (oldRevID != null && docID == null) || (body != null && docID == null))
            {
                throw new CouchbaseLiteException(StatusCode.BadRequest);
            }

            BeginTransaction();

            try
            {
                var oldRev = new RevisionInternal(docID, oldRevID, false, this);
                if (oldRevID != null)
                {
                    // Load existing revision if this is a replacement:
                    try
                    {
                        LoadRevisionBody(oldRev, DocumentContentOptions.None);
                    }
                    catch (CouchbaseLiteException e)
                    {
                        if (e.GetCBLStatus().GetCode() == StatusCode.NotFound && ExistsDocumentWithIDAndRev(docID, null))
                        {
                            throw new CouchbaseLiteException(StatusCode.Conflict);
                        }
                    }
                }
                else
                {
                    // If this creates a new doc, it needs a body:
                    oldRev.SetBody(new Body(new Dictionary<string, object>()));
                }
                // Update the _attachments dictionary:
                var oldRevProps = oldRev.GetProperties();
                IDictionary<string, object> attachments = null;
                if (oldRevProps != null)
                {
                    attachments = (IDictionary<string, object>)oldRevProps.Get("_attachments");
                }
                if (attachments == null)
                {
                    attachments = new Dictionary<string, object>();
                }
                if (body != null)
                {
                    var key = body.GetBlobKey();
                    var digest = key.Base64Digest();

                    var blobsByDigest = new Dictionary<string, BlobStoreWriter>();
                    blobsByDigest.Put(digest, body);

                    RememberAttachmentWritersForDigests(blobsByDigest);

                    var encodingName = (encoding == AttachmentEncoding.AttachmentEncodingGZIP) ? "gzip" : null;
                    var dict = new Dictionary<string, object>();
                    dict.Put("digest", digest);
                    dict.Put("length", body.GetLength());
                    dict.Put("follows", true);
                    dict.Put("content_type", contentType);
                    dict.Put("encoding", encodingName);

                    attachments.Put(filename, dict);
                }
                else
                {
                    if (oldRevID != null && !attachments.ContainsKey(filename))
                    {
                        throw new CouchbaseLiteException(StatusCode.NotFound);
                    }
                    attachments.Remove(filename);
                }

                var properties = oldRev.GetProperties();
                properties.Put("_attachments", attachments);
                oldRev.SetProperties(properties);

                // Create a new revision:
                var putStatus = new Status();
                var newRev = PutRevision(oldRev, oldRevID, false, putStatus);

                isSuccessful = true;

                return newRev;
            }
            catch (SQLException e)
            {
                Log.E(Tag, "Error updating attachment", e);
                throw new CouchbaseLiteException(StatusCode.InternalServerError);
            }
            finally
            {
                EndTransaction(isSuccessful);
            }
        }
コード例 #12
0
        internal bool ProcessAttachmentsForRevision(RevisionInternal rev, string prevRevId, Status status)
        {
            if (status == null) {
                status = new Status();
            }

            status.Code = StatusCode.Ok;
            var revAttachments = rev.GetAttachments();
            if (revAttachments == null) {
                return true; // no-op: no attachments
            }

            // Deletions can't have attachments:
            if (rev.IsDeleted() || revAttachments.Count == 0) {
                var body = rev.GetProperties();
                body.Remove("_attachments");
                rev.SetProperties(body);
                return true;
            }

            int generation = RevisionInternal.GenerationFromRevID(prevRevId) + 1;
            IDictionary<string, object> parentAttachments = null;
            return rev.MutateAttachments((name, attachInfo) =>
            {
                AttachmentInternal attachment = null;
                try {
                    attachment = new AttachmentInternal(name, attachInfo);
                } catch(CouchbaseLiteException e) {
                    return null;
                }

                if(attachment.EncodedContent != null) {
                    // If there's inline attachment data, decode and store it:
                    BlobKey blobKey = new BlobKey();
                    if(!Attachments.StoreBlob(attachment.EncodedContent.ToArray(), blobKey)) {
                        status.Code = StatusCode.AttachmentError;
                        return null;
                    }

                    attachment.BlobKey = blobKey;
                } else if(attachInfo.GetCast<bool>("follows")) {
                    // "follows" means the uploader provided the attachment in a separate MIME part.
                    // This means it's already been registered in _pendingAttachmentsByDigest;
                    // I just need to look it up by its "digest" property and install it into the store:
                    InstallAttachment(attachment, attachInfo);
                } else if(attachInfo.GetCast<bool>("stub")) {
                    // "stub" on an incoming revision means the attachment is the same as in the parent.
                    if(parentAttachments == null && prevRevId != null) {
                        parentAttachments = GetAttachmentsFromDoc(rev.GetDocId(), prevRevId, status);
                        if(parentAttachments == null) {
                            if(status.Code == StatusCode.Ok || status.Code == StatusCode.NotFound) {
                                status.Code = StatusCode.BadAttachment;
                            }

                            return null;
                        }
                    }

                    var parentAttachment = parentAttachments == null ? null : parentAttachments.Get(name).AsDictionary<string, object>();
                    if(parentAttachment == null) {
                        status.Code = StatusCode.BadAttachment;
                        return null;
                    }

                    return parentAttachment;
                }


                // Set or validate the revpos:
                if(attachment.RevPos == 0) {
                    attachment.RevPos = generation;
                } else if(attachment.RevPos >= generation) {
                    status.Code = StatusCode.BadAttachment;
                    return null;
                }

                Debug.Assert(attachment.IsValid);
                return attachment.AsStubDictionary();
            });
        }
コード例 #13
0
        internal bool ProcessAttachmentsForRevision(RevisionInternal rev, IList<string> ancestry)
        {
            var revAttachments = rev.GetAttachments();
            if (revAttachments == null) {
                return true; // no-op: no attachments
            }

            // Deletions can't have attachments:
            if (rev.IsDeleted() || revAttachments.Count == 0) {
                var body = rev.GetProperties();
                body.Remove("_attachments");
                rev.SetProperties(body);
                return true;
            }

            var prevRevId = ancestry != null && ancestry.Count > 0 ? ancestry[0] : null;
            int generation = RevisionInternal.GenerationFromRevID(prevRevId) + 1;
            IDictionary<string, object> parentAttachments = null;
            return rev.MutateAttachments((name, attachInfo) =>
            {
                AttachmentInternal attachment = null;
                try {
                    attachment = new AttachmentInternal(name, attachInfo);
                } catch(CouchbaseLiteException) {
                    return null;
                }

                if(attachment.EncodedContent != null) {
                    // If there's inline attachment data, decode and store it:
                    BlobKey blobKey = new BlobKey();
                    if(!Attachments.StoreBlob(attachment.EncodedContent.ToArray(), blobKey)) {
                        throw new CouchbaseLiteException(
                            String.Format("Failed to write attachment ' {0}'to disk", name), StatusCode.AttachmentError);
                    }

                    attachment.BlobKey = blobKey;
                } else if(attachInfo.GetCast<bool>("follows")) {
                    // "follows" means the uploader provided the attachment in a separate MIME part.
                    // This means it's already been registered in _pendingAttachmentsByDigest;
                    // I just need to look it up by its "digest" property and install it into the store:
                    InstallAttachment(attachment);
                } else if(attachInfo.GetCast<bool>("stub")) {
                    // "stub" on an incoming revision means the attachment is the same as in the parent.
                    if(parentAttachments == null && prevRevId != null) {
                        parentAttachments = GetAttachmentsFromDoc(rev.GetDocId(), prevRevId);
                        if(parentAttachments == null) {
                            if(Attachments.HasBlobForKey(attachment.BlobKey)) {
                                // Parent revision's body isn't known (we are probably pulling a rev along
                                // with its entire history) but it's OK, we have the attachment already
                                return attachInfo;
                            }

                            var ancestorAttachment = FindAttachment(name, attachment.RevPos, rev.GetDocId(), ancestry);
                            if(ancestorAttachment != null) {
                                return ancestorAttachment;
                            }

                            throw new CouchbaseLiteException(
                                String.Format("Unable to find 'stub' attachment {0} in history", name), StatusCode.BadAttachment);
                        }
                    }

                    var parentAttachment = parentAttachments == null ? null : parentAttachments.Get(name).AsDictionary<string, object>();
                    if(parentAttachment == null) {
                        throw new CouchbaseLiteException(
                            String.Format("Unable to find 'stub' attachment {0} in history", name), StatusCode.BadAttachment);
                    }

                    return parentAttachment;
                }


                // Set or validate the revpos:
                if(attachment.RevPos == 0) {
                    attachment.RevPos = generation;
                } else if(attachment.RevPos > generation) {
                    throw new CouchbaseLiteException(
                        String.Format("Attachment specifies revision generation {0} but document is only at revision generation {1}",
                        attachment.RevPos, generation), StatusCode.BadAttachment);
                }

                Debug.Assert(attachment.IsValid);
                return attachment.AsStubDictionary();
            });
        }
コード例 #14
0
        /// <summary>Inserts the _id, _rev and _attachments properties into the JSON data and stores it in rev.
        ///     </summary>
        /// <remarks>
        /// Inserts the _id, _rev and _attachments properties into the JSON data and stores it in rev.
        /// Rev must already have its revID and sequence properties set.
        /// </remarks>
        internal void ExpandStoredJSONIntoRevisionWithAttachments(IEnumerable<Byte> json, RevisionInternal rev, DocumentContentOptions contentOptions)
        {
            var extra = ExtraPropertiesForRevision(rev, contentOptions);

            if (json != null && json.Any())
            {
                rev.SetJson(AppendDictToJSON(json, extra));
            }
            else
            {
                rev.SetProperties(extra);
                if (json == null)
                {
                    rev.SetMissing(true);
                }
            }
        }
コード例 #15
0
        public void TestRevTree()
        {
            var change = default(DocumentChange);
            database.Changed += (sender, args) =>
            {
                Assert.AreEqual(1, args.Changes.Count());
                Assert.IsNull(change, "Multiple notifications posted");
                change = args.Changes.First();
            };

            var rev = new RevisionInternal("MyDocId", "4-4444".AsRevID(), false);
            var revProperties = new Dictionary<string, object>();
            revProperties.SetDocRevID(rev.DocID, rev.RevID);
            revProperties["message"] = "hi";
            rev.SetProperties(revProperties);

            var revHistory = new List<RevisionID>();
            revHistory.Add(rev.RevID);
            revHistory.Add("3-3333".AsRevID());
            revHistory.Add("2-2222".AsRevID());
            revHistory.Add("1-1111".AsRevID());
            database.ForceInsert(rev, revHistory, null);
            Assert.AreEqual(1, database.GetDocumentCount());
            VerifyRev(rev, revHistory);
            Assert.AreEqual(Announcement(database, rev, rev), change);
            Assert.IsFalse(change.IsConflict);

            // No-op ForceInsert of already-existing revision
            var lastSeq = database.GetLastSequenceNumber();
            database.ForceInsert(rev, revHistory, null);
            Assert.AreEqual(lastSeq, database.GetLastSequenceNumber());
            
            var conflict = new RevisionInternal("MyDocId", "5-5555".AsRevID(), false);
            var conflictProperties = new Dictionary<string, object>();
            conflictProperties.SetDocRevID(conflict.DocID, conflict.RevID);
            conflictProperties["message"] = "yo";
            conflict.SetProperties(conflictProperties);
            
            var conflictHistory = new List<RevisionID>();
            conflictHistory.Add(conflict.RevID);
            conflictHistory.Add("4-4545".AsRevID());
            conflictHistory.Add("3-3030".AsRevID());
            conflictHistory.Add("2-2222".AsRevID());
            conflictHistory.Add("1-1111".AsRevID());
            change = null;
            database.ForceInsert(conflict, conflictHistory, null);
            Assert.AreEqual(1, database.GetDocumentCount());
            VerifyRev(conflict, conflictHistory);
            Assert.AreEqual(Announcement(database, conflict, conflict), change);
            Assert.IsTrue(change.IsConflict);

            // Add an unrelated document:
            var other = new RevisionInternal("AnotherDocID", "1-1010".AsRevID(), false);
            var otherProperties = new Dictionary<string, object>();
            otherProperties["language"] = "jp";
            other.SetProperties(otherProperties);
            var otherHistory = new List<RevisionID>();
            otherHistory.Add(other.RevID);
            change = null;
            database.ForceInsert(other, otherHistory, null);
            Assert.AreEqual(Announcement(database, other, other), change);
            Assert.IsFalse(change.IsConflict);

            // Fetch one of those phantom revisions with no body:
            var rev2 = database.GetDocument(rev.DocID, "2-2222".AsRevID(), 
                true);
            Assert.IsTrue(rev2.Missing);
            Assert.IsNull(rev2.GetBody());

            Assert.IsNull(database.GetDocument(rev.DocID, "666-6666".AsRevID(), true));

            // Make sure no duplicate rows were inserted for the common revisions:
            if(_storageType == StorageEngineTypes.SQLite) {
                Assert.AreEqual(8, database.GetLastSequenceNumber());
            } else {
                Assert.AreEqual(3, database.GetLastSequenceNumber());
            }
            // Make sure the revision with the higher revID wins the conflict:
            var current = database.GetDocument(rev.DocID, null, 
                true);
            Assert.AreEqual(conflict, current);

            // Check that the list of conflicts is accurate
            var conflictingRevs = database.Storage.GetAllDocumentRevisions(rev.DocID, true, false);
            CollectionAssert.AreEqual(new[] { conflict, rev }, conflictingRevs);
            
            // Get the _changes feed and verify only the winner is in it:
            var options = new ChangesOptions();
            var changes = database.ChangesSince(0, options, null, null);
            CollectionAssert.AreEqual(new[] { conflict, other }, changes);
            options.IncludeConflicts = true;
            changes = database.ChangesSince(0, options, null, null);
            var expectedChanges = new RevisionList();
            expectedChanges.Add(rev);
            expectedChanges.Add(conflict);
            expectedChanges.Add(other);
            var expectedChangesAlt = new RevisionList();
            expectedChangesAlt.Add(conflict);
            expectedChangesAlt.Add(rev);
            expectedChangesAlt.Add(other);
            Assert.IsTrue(expectedChanges.SequenceEqual(changes) || expectedChangesAlt.SequenceEqual(changes));
        }
コード例 #16
0
        internal RevisionInternal PutDocument(string docId, IDictionary<string, object> properties, string prevRevId, bool allowConflict)
        {
            bool deleting = properties == null || properties.GetCast<bool>("_deleted");
            Log.D(TAG, "PUT _id={0}, _rev={1}, _deleted={2}, allowConflict={3}", docId, prevRevId, deleting, allowConflict);
            if ((prevRevId != null && docId == null) || (deleting && docId == null)) {
                throw new CouchbaseLiteException(StatusCode.BadId);
            }

            if (properties != null && properties.Get("_attachments").AsDictionary<string, object>() != null) {
                var tmpRev = new RevisionInternal(docId, prevRevId, deleting);
                tmpRev.SetProperties(properties);
                if (!ProcessAttachmentsForRevision(tmpRev, prevRevId == null ? null : new List<string> { prevRevId })) {
                    return null;
                }

                properties = tmpRev.GetProperties();
            }

            StoreValidation validationBlock = null;
            if (Shared.HasValues("validation", Name)) {
                validationBlock = ValidateRevision;
            }

            var putRev = Storage.PutRevision(docId, prevRevId, properties, deleting, allowConflict, validationBlock);
            if (putRev != null) {
                Log.D(TAG, "--> created {0}", putRev);
                if (!string.IsNullOrEmpty(docId)) {
                    var dummy = default(WeakReference);
                    UnsavedRevisionDocumentCache.TryRemove(docId, out dummy);
                }
            }

            return putRev;
        }
コード例 #17
0
ファイル: Database.cs プロジェクト: Redth/couchbase-lite-net
		public bool PutLocalDocument(string id, IDictionary<string, object> properties)
		{
			// TODO: the iOS implementation wraps this in a transaction, this should do the same.
			id = MakeLocalDocumentId(id);
			RevisionInternal prevRev = GetLocalDocument(id, null);
			if (prevRev == null && properties == null)
			{
				return false;
			}
			bool deleted = false;
			if (properties == null)
			{
				deleted = true;
			}
			RevisionInternal rev = new RevisionInternal(id, null, deleted, this);
			if (properties != null)
			{
				rev.SetProperties(properties);
			}
			if (prevRev == null)
			{
				return PutLocalRevision(rev, null) != null;
			}
			else
			{
				return PutLocalRevision(rev, prevRev.GetRevId()) != null;
			}
		}
コード例 #18
0
        public void TestPulledChangesAreExternal()
        {
            var changeNotifications = 0;

            EventHandler<DatabaseChangeEventArgs> handler = (sender, e) =>
            {
                changeNotifications++;
                Assert.IsTrue(e.IsExternal);
            };

            database.Changed += handler;

            // Insert a dcoument as if it came from a remote source.
            var rev = new RevisionInternal("docId", "1-rev", false, database);
            var properties = new Dictionary<string, object>();
            properties["_id"] = rev.GetDocId();
            properties["_rev"] = rev.GetRevId();
            rev.SetProperties(properties);

            var history = new List<string>();
            history.Add(rev.GetRevId());
            database.ForceInsert(rev, history, GetReplicationURL());

            Assert.AreEqual(1, changeNotifications);

            // Analysis disable once DelegateSubtraction
            database.Changed -= handler;
        }
コード例 #19
0
		/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
		public virtual void TestRevTree()
		{
			RevisionInternal rev = new RevisionInternal("MyDocId", "4-foxy", false, database);
			IDictionary<string, object> revProperties = new Dictionary<string, object>();
			revProperties.Put("_id", rev.GetDocId());
			revProperties.Put("_rev", rev.GetRevId());
			revProperties.Put("message", "hi");
			rev.SetProperties(revProperties);
			IList<string> revHistory = new AList<string>();
			revHistory.AddItem(rev.GetRevId());
			revHistory.AddItem("3-thrice");
			revHistory.AddItem("2-too");
			revHistory.AddItem("1-won");
			database.ForceInsert(rev, revHistory, null);
			NUnit.Framework.Assert.AreEqual(1, database.GetDocumentCount());
			VerifyHistory(database, rev, revHistory);
			RevisionInternal conflict = new RevisionInternal("MyDocId", "5-epsilon", false, database
				);
			IDictionary<string, object> conflictProperties = new Dictionary<string, object>();
			conflictProperties.Put("_id", conflict.GetDocId());
			conflictProperties.Put("_rev", conflict.GetRevId());
			conflictProperties.Put("message", "yo");
			conflict.SetProperties(conflictProperties);
			IList<string> conflictHistory = new AList<string>();
			conflictHistory.AddItem(conflict.GetRevId());
			conflictHistory.AddItem("4-delta");
			conflictHistory.AddItem("3-gamma");
			conflictHistory.AddItem("2-too");
			conflictHistory.AddItem("1-won");
			IList wasInConflict = new ArrayList();
			Database.ChangeListener listener = new _ChangeListener_84(wasInConflict);
			database.AddChangeListener(listener);
			database.ForceInsert(conflict, conflictHistory, null);
			NUnit.Framework.Assert.IsTrue(wasInConflict.Count > 0);
			database.RemoveChangeListener(listener);
			NUnit.Framework.Assert.AreEqual(1, database.GetDocumentCount());
			VerifyHistory(database, conflict, conflictHistory);
			// Add an unrelated document:
			RevisionInternal other = new RevisionInternal("AnotherDocID", "1-ichi", false, database
				);
			IDictionary<string, object> otherProperties = new Dictionary<string, object>();
			otherProperties.Put("language", "jp");
			other.SetProperties(otherProperties);
			IList<string> otherHistory = new AList<string>();
			otherHistory.AddItem(other.GetRevId());
			database.ForceInsert(other, otherHistory, null);
			// Fetch one of those phantom revisions with no body:
			RevisionInternal rev2 = database.GetDocumentWithIDAndRev(rev.GetDocId(), "2-too", 
				EnumSet.NoneOf<Database.TDContentOptions>());
			NUnit.Framework.Assert.AreEqual(rev.GetDocId(), rev2.GetDocId());
			NUnit.Framework.Assert.AreEqual("2-too", rev2.GetRevId());
			//Assert.assertNull(rev2.getContent());
			// Make sure no duplicate rows were inserted for the common revisions:
			NUnit.Framework.Assert.AreEqual(8, database.GetLastSequenceNumber());
			// Make sure the revision with the higher revID wins the conflict:
			RevisionInternal current = database.GetDocumentWithIDAndRev(rev.GetDocId(), null, 
				EnumSet.NoneOf<Database.TDContentOptions>());
			NUnit.Framework.Assert.AreEqual(conflict, current);
			// Get the _changes feed and verify only the winner is in it:
			ChangesOptions options = new ChangesOptions();
			RevisionList changes = database.ChangesSince(0, options, null);
			RevisionList expectedChanges = new RevisionList();
			expectedChanges.AddItem(conflict);
			expectedChanges.AddItem(other);
			NUnit.Framework.Assert.AreEqual(changes, expectedChanges);
			options.SetIncludeConflicts(true);
			changes = database.ChangesSince(0, options, null);
			expectedChanges = new RevisionList();
			expectedChanges.AddItem(rev);
			expectedChanges.AddItem(conflict);
			expectedChanges.AddItem(other);
			NUnit.Framework.Assert.AreEqual(changes, expectedChanges);
		}
コード例 #20
0
        public void TestRevTree()
        {
            var rev = new RevisionInternal("MyDocId", "4-abcd", false);
            var revProperties = new Dictionary<string, object>();
            revProperties.Put("_id", rev.GetDocId());
            revProperties.Put("_rev", rev.GetRevId());
            revProperties["message"] = "hi";
            rev.SetProperties(revProperties);

            var revHistory = new List<string>();
            revHistory.AddItem(rev.GetRevId());
            revHistory.AddItem("3-abcd");
            revHistory.AddItem("2-abcd");
            revHistory.AddItem("1-abcd");
            database.ForceInsert(rev, revHistory, null);
            Assert.AreEqual(1, database.DocumentCount);

            VerifyHistory(database, rev, revHistory);
            var conflict = new RevisionInternal("MyDocId", "5-abcd", false);
            var conflictProperties = new Dictionary<string, object>();
            conflictProperties.Put("_id", conflict.GetDocId());
            conflictProperties.Put("_rev", conflict.GetRevId());
            conflictProperties["message"] = "yo";
            conflict.SetProperties(conflictProperties);
            
            var conflictHistory = new List<string>();
            conflictHistory.AddItem(conflict.GetRevId());
            conflictHistory.AddItem("4-bcde");
            conflictHistory.AddItem("3-bcde");
            conflictHistory.AddItem("2-abcd");
            conflictHistory.AddItem("1-abcd");
            database.ForceInsert(conflict, conflictHistory, null);
            Assert.AreEqual(1, database.DocumentCount);
            VerifyHistory(database, conflict, conflictHistory);
            
            // Add an unrelated document:
            var other = new RevisionInternal("AnotherDocID", "1-cdef", false);
            var otherProperties = new Dictionary<string, object>();
            otherProperties["language"] = "jp";
            other.SetProperties(otherProperties);
            var otherHistory = new List<string>();
            otherHistory.AddItem(other.GetRevId());
            database.ForceInsert(other, otherHistory, null);
            
            // Fetch one of those phantom revisions with no body:
            var rev2 = database.GetDocument(rev.GetDocId(), "2-abcd", 
                true);
            Assert.IsNull(rev2);

            // Make sure no duplicate rows were inserted for the common revisions:
            Assert.IsTrue(database.LastSequenceNumber <= 8);
            // Make sure the revision with the higher revID wins the conflict:
            var current = database.GetDocument(rev.GetDocId(), null, 
                true);
            Assert.AreEqual(conflict, current);
            
            // Get the _changes feed and verify only the winner is in it:
            var options = new ChangesOptions();
            var changes = database.ChangesSince(0, options, null, null);
            var expectedChanges = new RevisionList();
            expectedChanges.AddItem(conflict);
            expectedChanges.AddItem(other);
            Assert.AreEqual(expectedChanges, changes);
            options.IncludeConflicts = true;
            changes = database.ChangesSince(0, options, null, null);
            expectedChanges = new RevisionList();
            expectedChanges.AddItem(rev);
            expectedChanges.AddItem(conflict);
            expectedChanges.AddItem(other);
            var expectedChangesAlt = new RevisionList();
            expectedChangesAlt.AddItem(conflict);
            expectedChangesAlt.AddItem(rev);
            expectedChangesAlt.AddItem(other);
            Assert.IsTrue(expectedChanges.SequenceEqual(changes) || expectedChangesAlt.SequenceEqual(changes));
        }
コード例 #21
0
        /// <summary>
        /// Sets the contents of the local <see cref="Couchbase.Lite.Document" /> with the given id.  If <param name="properties"/> is null, the 
        /// <see cref="Couchbase.Lite.Document" /> is deleted.
        /// </summary>
        /// <param name="id">The id of the local document whos contents to set.</param>
        /// <param name="properties">The contents to set for the local document.</param>
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException">Thrown if an issue occurs 
        /// while setting the contents of the local document.</exception>
        public bool PutLocalDocument(string id, IDictionary<string, object> properties) 
        { 
            if (!IsOpen) {
                Log.W(TAG, "PutLocalDocument called on closed database");
                return false;
            }

            id = MakeLocalDocumentId(id);
            var rev = new RevisionInternal(id, null, properties == null);
            if (properties != null) {
                rev.SetProperties(properties);
            }
                
            bool ok = Storage.PutLocalRevision(rev, null, false) != null;
            return ok;
        }
コード例 #22
0
        internal RevisionInternal GetLocalDocument(string docID, string revID)
        {
            // docID already should contain "_local/" prefix
            RevisionInternal result = null;
            Cursor cursor = null;
            try
            {
                var args = new [] { docID };
                cursor = StorageEngine.RawQuery("SELECT revid, json FROM localdocs WHERE docid=?", CommandBehavior.SequentialAccess, args);

                if (cursor.MoveToNext())
                {
                    var gotRevID = cursor.GetString(0);
                    if (revID != null && (!revID.Equals(gotRevID)))
                    {
                        return null;
                    }

                    var json = cursor.GetBlob(1);
                    IDictionary<string, object> properties = null;
                    try
                    {
                        properties = Manager.GetObjectMapper().ReadValue<IDictionary<String, Object>>(json);
                        properties["_id"] = docID;
                        properties["_rev"] = gotRevID;

                        result = new RevisionInternal(docID, gotRevID, false, this);
                        result.SetProperties(properties);
                    }
                    catch (Exception e)
                    {
                        Log.W(Tag, "Error parsing local doc JSON", e);
                        return null;
                    }
                }
                return result;
            }
            catch (SQLException e)
            {
                Log.E(Tag, "Error getting local document", e);
                return null;
            }
            finally
            {
                if (cursor != null)
                {
                    cursor.Close();
                }
            }
        }
コード例 #23
0
		/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
		public virtual void TestValidations()
		{
			Validator validator = new _Validator_19(this);
			database.SetValidation("hoopy", validator);
			// POST a valid new document:
			IDictionary<string, object> props = new Dictionary<string, object>();
			props.Put("name", "Zaphod Beeblebrox");
			props.Put("towel", "velvet");
			RevisionInternal rev = new RevisionInternal(props, database);
			Status status = new Status();
			validationCalled = false;
			rev = database.PutRevision(rev, null, false, status);
			NUnit.Framework.Assert.IsTrue(validationCalled);
			NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode());
			// PUT a valid update:
			props.Put("head_count", 3);
			rev.SetProperties(props);
			validationCalled = false;
			rev = database.PutRevision(rev, rev.GetRevId(), false, status);
			NUnit.Framework.Assert.IsTrue(validationCalled);
			NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode());
			// PUT an invalid update:
			Sharpen.Collections.Remove(props, "towel");
			rev.SetProperties(props);
			validationCalled = false;
			bool gotExpectedError = false;
			try
			{
				rev = database.PutRevision(rev, rev.GetRevId(), false, status);
			}
			catch (CouchbaseLiteException e)
			{
				gotExpectedError = (e.GetCBLStatus().GetCode() == Status.Forbidden);
			}
			NUnit.Framework.Assert.IsTrue(validationCalled);
			NUnit.Framework.Assert.IsTrue(gotExpectedError);
			// POST an invalid new document:
			props = new Dictionary<string, object>();
			props.Put("name", "Vogon");
			props.Put("poetry", true);
			rev = new RevisionInternal(props, database);
			validationCalled = false;
			gotExpectedError = false;
			try
			{
				rev = database.PutRevision(rev, null, false, status);
			}
			catch (CouchbaseLiteException e)
			{
				gotExpectedError = (e.GetCBLStatus().GetCode() == Status.Forbidden);
			}
			NUnit.Framework.Assert.IsTrue(validationCalled);
			NUnit.Framework.Assert.IsTrue(gotExpectedError);
			// PUT a valid new document with an ID:
			props = new Dictionary<string, object>();
			props.Put("_id", "ford");
			props.Put("name", "Ford Prefect");
			props.Put("towel", "terrycloth");
			rev = new RevisionInternal(props, database);
			validationCalled = false;
			rev = database.PutRevision(rev, null, false, status);
			NUnit.Framework.Assert.IsTrue(validationCalled);
			NUnit.Framework.Assert.AreEqual("ford", rev.GetDocId());
			// DELETE a document:
			rev = new RevisionInternal(rev.GetDocId(), rev.GetRevId(), true, database);
			NUnit.Framework.Assert.IsTrue(rev.IsDeleted());
			validationCalled = false;
			rev = database.PutRevision(rev, rev.GetRevId(), false, status);
			NUnit.Framework.Assert.IsTrue(validationCalled);
			// PUT an invalid new document:
			props = new Dictionary<string, object>();
			props.Put("_id", "petunias");
			props.Put("name", "Pot of Petunias");
			rev = new RevisionInternal(props, database);
			validationCalled = false;
			gotExpectedError = false;
			try
			{
				rev = database.PutRevision(rev, null, false, status);
			}
			catch (CouchbaseLiteException e)
			{
				gotExpectedError = (e.GetCBLStatus().GetCode() == Status.Forbidden);
			}
			NUnit.Framework.Assert.IsTrue(validationCalled);
			NUnit.Framework.Assert.IsTrue(gotExpectedError);
		}
コード例 #24
0
        /// <summary>
        /// Sets the contents of the local <see cref="Couchbase.Lite.Document" /> with the given id.  If <param name="properties"/> is null, the 
        /// <see cref="Couchbase.Lite.Document" /> is deleted.
        /// </summary>
        /// <param name="id">The id of the local document whos contents to set.</param>
        /// <param name="properties">The contents to set for the local document.</param>
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException">Thrown if an issue occurs 
        /// while setting the contents of the local document.</exception>
        public void PutLocalDocument(String id, IDictionary<String, Object> properties) 
        { 
            // TODO: the iOS implementation wraps this in a transaction, this should do the same.
            id = MakeLocalDocumentId(id);
            var prevRev = GetLocalDocument(id, null);

            if (prevRev == null && properties == null)
            {
                return;
            }

            var deleted = false || properties == null;
            var rev = new RevisionInternal(id, null, deleted, this);

            if (properties != null)
            {
                rev.SetProperties(properties);
            }

            var success = false;

            if (prevRev == null)
            {
                success = PutLocalRevision(rev, null) != null;
            }
            else
            {
                success = PutLocalRevision(rev, prevRev.GetRevId()) != null;
            }

            if (!success) 
            {
                throw new CouchbaseLiteException("Unable to put local revision with id " + id);
            }
        }
コード例 #25
0
        public void TestRevTree()
        {
            var rev = new RevisionInternal("MyDocId", "4-foxy", false, database);
            var revProperties = new Dictionary<string, object>();
            revProperties.Put("_id", rev.GetDocId());
            revProperties.Put("_rev", rev.GetRevId());
            revProperties["message"] = "hi";
            rev.SetProperties(revProperties);

            var revHistory = new AList<string>();
            revHistory.AddItem(rev.GetRevId());
            revHistory.AddItem("3-thrice");
            revHistory.AddItem("2-too");
            revHistory.AddItem("1-won");
            database.ForceInsert(rev, revHistory, null);
            Assert.AreEqual(1, database.DocumentCount);

            VerifyHistory(database, rev, revHistory);
            var conflict = new RevisionInternal("MyDocId", "5-epsilon", false, database);
            var conflictProperties = new Dictionary<string, object>();
            conflictProperties.Put("_id", conflict.GetDocId());
            conflictProperties.Put("_rev", conflict.GetRevId());
            conflictProperties["message"] = "yo";
            conflict.SetProperties(conflictProperties);
            
            var conflictHistory = new AList<string>();
            conflictHistory.AddItem(conflict.GetRevId());
            conflictHistory.AddItem("4-delta");
            conflictHistory.AddItem("3-gamma");
            conflictHistory.AddItem("2-too");
            conflictHistory.AddItem("1-won");
            database.ForceInsert(conflict, conflictHistory, null);
            Assert.AreEqual(1, database.DocumentCount);
            VerifyHistory(database, conflict, conflictHistory);
            
            // Add an unrelated document:
            var other = new RevisionInternal("AnotherDocID", "1-ichi", false, database);
            var otherProperties = new Dictionary<string, object>();
            otherProperties["language"] = "jp";
            other.SetProperties(otherProperties);
            var otherHistory = new AList<string>();
            otherHistory.AddItem(other.GetRevId());
            database.ForceInsert(other, otherHistory, null);
            
            // Fetch one of those phantom revisions with no body:
            var rev2 = database.GetDocumentWithIDAndRev(rev.GetDocId(), "2-too", 
                DocumentContentOptions.None);
            Assert.AreEqual(rev.GetDocId(), rev2.GetDocId());
            Assert.AreEqual("2-too", rev2.GetRevId());
            
            // Make sure no duplicate rows were inserted for the common revisions:
            Assert.AreEqual(8, database.GetLastSequenceNumber());
            // Make sure the revision with the higher revID wins the conflict:
            var current = database.GetDocumentWithIDAndRev(rev.GetDocId(), null, 
                DocumentContentOptions.None);
            Assert.AreEqual(conflict, current);
            
            // Get the _changes feed and verify only the winner is in it:
            var options = new ChangesOptions();
            var changes = database.ChangesSince(0, options, null);
            var expectedChanges = new RevisionList();
            expectedChanges.AddItem(conflict);
            expectedChanges.AddItem(other);
            Assert.AreEqual(changes, expectedChanges);
            options.SetIncludeConflicts(true);
            changes = database.ChangesSince(0, options, null);
            expectedChanges = new RevisionList();
            expectedChanges.AddItem(rev);
            expectedChanges.AddItem(conflict);
            expectedChanges.AddItem(other);
            Assert.AreEqual(changes, expectedChanges);
        }
コード例 #26
0
        internal RevisionInternal PutDocument(string docId, IDictionary<string, object> properties, string prevRevId, bool allowConflict, Status resultStatus)
        {
            bool deleting = properties == null || properties.GetCast<bool>("_deleted");
            Log.D(TAG, "PUT _id={0}, _rev={1}, _deleted={2}, allowConflict={3}", docId, prevRevId, deleting, allowConflict);
            if ((prevRevId != null && docId == null) || (deleting && docId == null)) {
                if (resultStatus != null) {
                    resultStatus.Code = StatusCode.BadId;
                    return null;
                }
            }

            if (properties != null && properties.Get("_attachments").AsDictionary<string, object>() != null) {
                var tmpRev = new RevisionInternal(docId, prevRevId, deleting);
                tmpRev.SetProperties(properties);
                if (!ProcessAttachmentsForRevision(tmpRev, prevRevId, resultStatus)) {
                    return null;
                }

                properties = tmpRev.GetProperties();
            }

            StoreValidation validationBlock = null;
            if (Shared.HasValues("validation", Name)) {
                validationBlock = ValidateRevision;
            }

            var putRev = Storage.PutRevision(docId, prevRevId, properties, deleting, allowConflict, validationBlock, resultStatus);
            if (putRev != null) {
                Log.D(TAG, "--> created {0}", putRev);
                if (!string.IsNullOrEmpty(docId)) {
                    UnsavedRevisionDocumentCache.Remove(docId);
                }
            }

            return putRev;
        }
コード例 #27
0
        public RevisionInternal GetLocalDocument(string docId, string revId)
        {
            if(!docId.StartsWith("_local/")) {
                return null;
            }

            var retVal = default(RevisionInternal);
            WithC4Raw(docId, "_local", doc =>
            {
                if(doc == null) {
                    return;
                }

                var gotRevId = (string)doc->meta;
                if(revId != null && revId != gotRevId || doc->body.size == 0) {
                    return;
                }

                var properties = default(IDictionary<string, object>);
                try {
                    properties = Manager.GetObjectMapper().ReadValue<IDictionary<string, object>>(doc->body);
                } catch(CouchbaseLiteException) {
                    Log.W(TAG, "Invalid JSON for document {0}", docId);
                    return;
                }

                properties["_id"] = docId;
                properties["_rev"] = gotRevId;
                retVal = new RevisionInternal(docId, revId, false);
                retVal.SetProperties(properties);
            });

            return retVal;
        }
コード例 #28
0
        /// <summary>Updates or deletes an attachment, creating a new document revision in the process.
        ///     </summary>
        /// <remarks>
        /// Updates or deletes an attachment, creating a new document revision in the process.
        /// Used by the PUT / DELETE methods called on attachment URLs.
        /// </remarks>
        /// <exclude></exclude>
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
        internal RevisionInternal UpdateAttachment(string filename, BlobStoreWriter body, string contentType, AttachmentEncoding encoding, string docID, string oldRevID)
        {
            if(StringEx.IsNullOrWhiteSpace(filename) || (body != null && contentType == null) || 
                (oldRevID != null && docID == null) || (body != null && docID == null)) {
                throw new CouchbaseLiteException(StatusCode.BadAttachment);
            }

            var oldRev = new RevisionInternal(docID, oldRevID, false);
            if (oldRevID != null) {
                // Load existing revision if this is a replacement:
                try {
                    oldRev = LoadRevisionBody(oldRev);
                } catch (CouchbaseLiteException e) {
                    if (e.Code == StatusCode.NotFound && GetDocument(docID, null, false) != null) {
                        throw new CouchbaseLiteException(StatusCode.Conflict);
                    }

                    throw;
                }
            } else {
                // If this creates a new doc, it needs a body:
                oldRev.SetBody(new Body(new Dictionary<string, object>()));
            }

            // Update the _attachments dictionary:
            var attachments = oldRev.GetProperties().Get("_attachments").AsDictionary<string, object>();
            if (attachments == null) {
                attachments = new Dictionary<string, object>();
            }

            if (body != null) {
                var key = body.GetBlobKey();
                string digest = key.Base64Digest();
                RememberAttachmentWriter(body);
                string encodingName = (encoding == AttachmentEncoding.GZIP) ? "gzip" : null;
                attachments[filename] = new NonNullDictionary<string, object> {
                    { "digest", digest },
                    { "length", body.GetLength() },
                    { "follows", true },
                    { "content_type", contentType },
                    { "encoding", encodingName }
                };
            } else {
                if (oldRevID != null && attachments.Get(filename) == null) {
                    throw new CouchbaseLiteException(StatusCode.AttachmentNotFound);
                }

                attachments.Remove(filename);
            }

            var properties = oldRev.GetProperties();
            properties["_attachments"] = attachments;
            oldRev.SetProperties(properties);

            Status status = new Status();
            var newRev = PutRevision(oldRev, oldRevID, false, status);
            if (status.IsError) {
                throw new CouchbaseLiteException(status.Code);
            }

            return newRev;
        }
コード例 #29
0
        public RevisionInternal PutRevision(string inDocId, string inPrevRevId, IDictionary<string, object> properties,
            bool deleting, bool allowConflict, StoreValidation validationBlock)
        {
            if(_config.HasFlag(C4DatabaseFlags.ReadOnly)) {
                throw new CouchbaseLiteException("Attempting to write to a readonly database", StatusCode.Forbidden);
            }

            var json = default(string);
            if (properties != null) {
                json = Manager.GetObjectMapper().WriteValueAsString(Database.StripDocumentJSON(properties), true);
            } else {
                json = "{}";
            }

            if (inDocId == null) {
                inDocId = Misc.CreateGUID();
            }

            var putRev = default(RevisionInternal);
            var change = default(DocumentChange);
            var success = RunInTransaction(() =>
            {
                var docId = inDocId;
                var prevRevId = inPrevRevId;
                var transactionSuccess = false;
                WithC4Document(docId, null, false, true, doc =>
                {
                    if(prevRevId != null) {
                        // Updating an existing revision; make sure it exists and is a leaf:
                        ForestDBBridge.Check(err => Native.c4doc_selectRevision(doc, prevRevId, false, err));
                        if(!allowConflict && !doc->selectedRev.IsLeaf) {
                            throw new CouchbaseLiteException(StatusCode.Conflict);
                        }
                    } else {
                        // No parent revision given:
                        if(deleting) {
                            // Didn't specify a revision to delete: NotFound or a Conflict, depending
                            throw new CouchbaseLiteException(doc->Exists ? StatusCode.Conflict : StatusCode.NotFound);
                        }

                        // If doc exists, current rev must be in a deleted state or there will be a conflict:
                        if(Native.c4doc_selectCurrentRevision(doc)) {
                            if(doc->selectedRev.IsDeleted) {
                                // New rev will be child of the tombstone:
                                prevRevId = (string)doc->revID;
                            } else {
                                throw new CouchbaseLiteException(StatusCode.Conflict);
                            }
                        }
                    }

                    // Compute the new revID. (Can't be done earlier because prevRevID may have changed.)
                    var newRevID = Delegate != null ? Delegate.GenerateRevID(Encoding.UTF8.GetBytes(json), deleting, prevRevId) : null;
                    if(newRevID == null) {
                        throw new CouchbaseLiteException(StatusCode.BadId);
                    }

                    putRev = new RevisionInternal(docId, newRevID, deleting);
                    if(properties != null) {
                        properties["_id"] = docId;
                        properties["_rev"] = newRevID;
                        putRev.SetProperties(properties);
                    }

                    // Run any validation blocks:
                    if(validationBlock != null) {
                        var prevRev = default(RevisionInternal);
                        if(prevRevId != null) {
                            prevRev = new RevisionInternal(docId, prevRevId, doc->selectedRev.IsDeleted);
                        }
                            
                        var status = validationBlock(putRev, prevRev, prevRevId);
                        if(status.IsError) {
                            throw new CouchbaseLiteException(String.Format("{0} failed validation", putRev), 
                                status.Code);
                        }
                    }

                    // Add the revision to the database:
                    ForestDBBridge.Check(err => Native.c4doc_insertRevision(doc, newRevID, json, deleting,
                        putRev.GetAttachments() != null, allowConflict, err));
                    var isWinner = SaveDocument(doc, newRevID, properties);
                    putRev.SetSequence((long)doc->sequence);
                    change = ChangeWithNewRevision(putRev, isWinner, doc, null);
                    transactionSuccess = true;
                });

                return transactionSuccess;
            });

            if (!success) {
                return null;
            }

            if (Delegate != null && change != null) {
                Delegate.DatabaseStorageChanged(change);
            }

            return putRev;
        }
コード例 #30
0
ファイル: Database.cs プロジェクト: Redth/couchbase-lite-net
		public void ExpandStoredJSONIntoRevisionWithAttachments(byte[] json, RevisionInternal
			 rev, EnumSet<Database.TDContentOptions> contentOptions)
		{
			IDictionary<string, object> extra = ExtraPropertiesForRevision(rev, contentOptions
				);
			if (json != null)
			{
				rev.SetJson(AppendDictToJSON(json, extra));
			}
			else
			{
				rev.SetProperties(extra);
			}
		}
コード例 #31
0
        public void TestRevTreeChangeNotification()
        {
            const string DOCUMENT_ID = "MyDocId";

            var rev = new RevisionInternal(DOCUMENT_ID, "1-abcd", false);
            var revProperties = new Dictionary<string, object>();
            revProperties["_id"] = rev.GetDocId();
            revProperties["_rev"] = rev.GetRevId();
            revProperties["message"] = "hi";
            rev.SetProperties(revProperties);

            var revHistory = new List<string>();
            revHistory.Add(rev.GetRevId());

            EventHandler<DatabaseChangeEventArgs> handler = (sender, e) =>
            {
                var changes = e.Changes.ToList();
                Assert.AreEqual(1, changes.Count);
                var change = changes[0];
                Assert.AreEqual(DOCUMENT_ID, change.DocumentId);
                Assert.AreEqual(rev.GetRevId(), change.RevisionId);
                Assert.IsTrue(change.IsCurrentRevision);
                Assert.IsFalse(change.IsConflict);

                var current = database.GetDocument(change.DocumentId).CurrentRevision;
                Assert.AreEqual(rev.GetRevId(), current.Id);
            };

            database.Changed += handler;
            database.ForceInsert(rev, revHistory, null);
            database.Changed -= handler;

            // add two more revisions to the document
            var rev3 = new RevisionInternal(DOCUMENT_ID, "3-abcd", false);
            var rev3Properties = new Dictionary<string, object>();
            rev3Properties["_id"] = rev3.GetDocId();
            rev3Properties["_rev"] = rev3.GetRevId();
            rev3Properties["message"] = "hi again";
            rev3.SetProperties(rev3Properties);

            var rev3History = new List<string>();
            rev3History.Add(rev3.GetRevId());
            rev3History.Add("2-abcd");
            rev3History.Add(rev.GetRevId());

            handler = (sender, e) =>
            {
                var changes = e.Changes.ToList();
                Assert.AreEqual(1, changes.Count);
                var change = changes[0];
                Assert.AreEqual(DOCUMENT_ID, change.DocumentId);
                Assert.AreEqual(rev3.GetRevId(), change.RevisionId);
                Assert.IsTrue(change.IsCurrentRevision);
                Assert.IsFalse(change.IsConflict);

                var doc = database.GetDocument(change.DocumentId);
                Assert.AreEqual(rev3.GetRevId(), doc.CurrentRevisionId);
                try
                {
                    Assert.AreEqual(3, doc.RevisionHistory.ToList().Count);
                }
                catch (CouchbaseLiteException)
                {
                    Assert.Fail();
                }
            };

            database.Changed += handler;
            database.ForceInsert(rev3, rev3History, null);
            database.Changed -= handler;

            // add a conflicting revision, with the same history length as the last revision we
            // inserted. Since this new revision's revID has a higher ASCII sort, it should become the
            // new winning revision.
            var conflictRev = new RevisionInternal(DOCUMENT_ID, "3-bcde", false);
            var conflictProperties = new Dictionary<string, object>();
            conflictProperties["_id"] = conflictRev.GetDocId();
            conflictProperties["_rev"] = conflictRev.GetRevId();
            conflictProperties["message"] = "winner";
            conflictRev.SetProperties(conflictProperties);

            var conflictRevHistory = new List<string>();
            conflictRevHistory.Add(conflictRev.GetRevId());
            conflictRevHistory.Add("2-abcd");
            conflictRevHistory.Add(rev.GetRevId());

            handler = (sender, e) =>
            {
                var changes = e.Changes.ToList();
                Assert.AreEqual(1, changes.Count);
                var change = changes[0];
                Assert.AreEqual(DOCUMENT_ID, change.DocumentId);
                Assert.AreEqual(conflictRev.GetRevId(), change.RevisionId);
                Assert.IsTrue(change.IsCurrentRevision);
                Assert.IsFalse(change.IsConflict);

                var doc = database.GetDocument(change.DocumentId);
                Assert.AreEqual(rev3.GetRevId(), doc.CurrentRevisionId);
                try
                {
                    Assert.AreEqual(2, doc.ConflictingRevisions.ToList().Count);
                    Assert.AreEqual(3, doc.RevisionHistory.ToList().Count);
                }
                catch (CouchbaseLiteException)
                {
                    Assert.Fail();
                }
            };

            database.Changed += handler;
            database.ForceInsert(conflictRev, conflictRevHistory, null);
            database.Changed -= handler;
        }
コード例 #32
0
		/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
		public virtual void TestValidations()
		{
            Database.ValidateDelegate validator = (Revision newRevision, ValidationContext context)=>
            {
                NUnit.Framework.Assert.IsNotNull(newRevision);
                NUnit.Framework.Assert.IsNotNull(context);
                NUnit.Framework.Assert.IsTrue(newRevision.Properties != null || newRevision.
                    IsDeletion);
                this._enclosing.validationCalled = true;
                bool hoopy = newRevision.IsDeletion || (newRevision.Properties.Get("towel"
                ) != null);
                Log.V(ValidationsTest.Tag, string.Format("--- Validating %s --> %b", newRevision.
                    Properties, hoopy));
                if (!hoopy)
                {
                    context.Reject("Where's your towel?");
                }
                return hoopy;
            };
			database.SetValidation("hoopy", validator);
			// POST a valid new document:
			IDictionary<string, object> props = new Dictionary<string, object>();
			props["name"] = "Zaphod Beeblebrox";
			props["towel"] = "velvet";
			RevisionInternal rev = new RevisionInternal(props, database);
			Status status = new Status();
			validationCalled = false;
			rev = database.PutRevision(rev, null, false, status);
			NUnit.Framework.Assert.IsTrue(validationCalled);
            NUnit.Framework.Assert.AreEqual(StatusCode.Created, status.GetCode());
			// PUT a valid update:
			props["head_count"] = 3;
			rev.SetProperties(props);
			validationCalled = false;
			rev = database.PutRevision(rev, rev.GetRevId(), false, status);
			NUnit.Framework.Assert.IsTrue(validationCalled);
            NUnit.Framework.Assert.AreEqual(StatusCode.Created, status.GetCode());
			// PUT an invalid update:
			Sharpen.Collections.Remove(props, "towel");
			rev.SetProperties(props);
			validationCalled = false;
			bool gotExpectedError = false;
			try
			{
				rev = database.PutRevision(rev, rev.GetRevId(), false, status);
			}
			catch (CouchbaseLiteException e)
			{
                gotExpectedError = (e.GetCBLStatus().GetCode() == StatusCode.Forbidden);
			}
			NUnit.Framework.Assert.IsTrue(validationCalled);
			NUnit.Framework.Assert.IsTrue(gotExpectedError);
			// POST an invalid new document:
			props = new Dictionary<string, object>();
			props["name"] = "Vogon";
			props["poetry"] = true;
			rev = new RevisionInternal(props, database);
			validationCalled = false;
			gotExpectedError = false;
			try
			{
				rev = database.PutRevision(rev, null, false, status);
			}
			catch (CouchbaseLiteException e)
			{
                gotExpectedError = (e.GetCBLStatus().GetCode() == StatusCode.Forbidden);
			}
			NUnit.Framework.Assert.IsTrue(validationCalled);
			NUnit.Framework.Assert.IsTrue(gotExpectedError);
			// PUT a valid new document with an ID:
			props = new Dictionary<string, object>();
			props["_id"] = "ford";
			props["name"] = "Ford Prefect";
			props["towel"] = "terrycloth";
			rev = new RevisionInternal(props, database);
			validationCalled = false;
			rev = database.PutRevision(rev, null, false, status);
			NUnit.Framework.Assert.IsTrue(validationCalled);
			NUnit.Framework.Assert.AreEqual("ford", rev.GetDocId());
			// DELETE a document:
			rev = new RevisionInternal(rev.GetDocId(), rev.GetRevId(), true, database);
			NUnit.Framework.Assert.IsTrue(rev.IsDeleted());
			validationCalled = false;
			rev = database.PutRevision(rev, rev.GetRevId(), false, status);
			NUnit.Framework.Assert.IsTrue(validationCalled);
			// PUT an invalid new document:
			props = new Dictionary<string, object>();
			props["_id"] = "petunias";
			props["name"] = "Pot of Petunias";
			rev = new RevisionInternal(props, database);
			validationCalled = false;
			gotExpectedError = false;
			try
			{
				rev = database.PutRevision(rev, null, false, status);
			}
			catch (CouchbaseLiteException e)
			{
                gotExpectedError = (e.GetCBLStatus().GetCode() == StatusCode.Forbidden);
			}
			NUnit.Framework.Assert.IsTrue(validationCalled);
			NUnit.Framework.Assert.IsTrue(gotExpectedError);
		}
コード例 #33
0
		/// <summary>
		/// Test that the public API works as expected in change notifications after a rev tree
		/// insertion.
		/// </summary>
		/// <remarks>
		/// Test that the public API works as expected in change notifications after a rev tree
		/// insertion.  See https://github.com/couchbase/couchbase-lite-android-core/pull/27
		/// </remarks>
		/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
		public virtual void TestRevTreeChangeNotifications()
		{
			string DocumentId = "MyDocId";
			// add a document with a single (first) revision
			RevisionInternal rev = new RevisionInternal(DocumentId, "1-one", false, database);
			IDictionary<string, object> revProperties = new Dictionary<string, object>();
			revProperties.Put("_id", rev.GetDocId());
			revProperties.Put("_rev", rev.GetRevId());
			revProperties.Put("message", "hi");
			rev.SetProperties(revProperties);
			IList<string> revHistory = Arrays.AsList(rev.GetRevId());
			Database.ChangeListener listener = new _ChangeListener_154(this, DocumentId, rev);
			database.AddChangeListener(listener);
			database.ForceInsert(rev, revHistory, null);
			database.RemoveChangeListener(listener);
			// add two more revisions to the document
			RevisionInternal rev3 = new RevisionInternal(DocumentId, "3-three", false, database
				);
			IDictionary<string, object> rev3Properties = new Dictionary<string, object>();
			rev3Properties.Put("_id", rev3.GetDocId());
			rev3Properties.Put("_rev", rev3.GetRevId());
			rev3Properties.Put("message", "hi again");
			rev3.SetProperties(rev3Properties);
			IList<string> rev3History = Arrays.AsList(rev3.GetRevId(), "2-two", rev.GetRevId(
				));
			listener = new _ChangeListener_182(this, DocumentId, rev3);
			database.AddChangeListener(listener);
			database.ForceInsert(rev3, rev3History, null);
			database.RemoveChangeListener(listener);
			// add a conflicting revision, with the same history length as the last revision we
			// inserted. Since this new revision's revID has a higher ASCII sort, it should become the
			// new winning revision.
			RevisionInternal conflictRev = new RevisionInternal(DocumentId, "3-winner", false
				, database);
			IDictionary<string, object> conflictProperties = new Dictionary<string, object>();
			conflictProperties.Put("_id", conflictRev.GetDocId());
			conflictProperties.Put("_rev", conflictRev.GetRevId());
			conflictProperties.Put("message", "winner");
			conflictRev.SetProperties(conflictProperties);
			IList<string> conflictRevHistory = Arrays.AsList(conflictRev.GetRevId(), "2-two", 
				rev.GetRevId());
			listener = new _ChangeListener_217(this, DocumentId, conflictRev);
			database.AddChangeListener(listener);
			database.ForceInsert(conflictRev, conflictRevHistory, null);
			database.RemoveChangeListener(listener);
		}