Ejemplo n.º 1
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-abcd", false);
            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;
        }
Ejemplo n.º 2
0
        public void TestPulledChangesAreExternal()
        {
            var countDown = new CountdownEvent(1);

            EventHandler <DatabaseChangeEventArgs> handler = (sender, e) =>
            {
                countDown.Signal();
                Assert.IsFalse(e.IsExternal);
            };

            database.Changed += handler;

            // Insert a dcoument as if it came from a remote source.
            var rev        = new RevisionInternal("docId", "1-abcd".AsRevID(), false);
            var properties = new Dictionary <string, object>();

            properties.SetDocRevID(rev.DocID, rev.RevID);
            rev.SetProperties(properties);

            var history = new List <RevisionID>();

            history.Add(rev.RevID);
            database.ForceInsert(rev, history, GetReplicationURL());

            Assert.IsTrue(countDown.Wait(TimeSpan.FromSeconds(1)));

            // Analysis disable once DelegateSubtraction
            database.Changed -= handler;
        }
Ejemplo n.º 3
0
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
        internal SavedRevision PutProperties(IDictionary <String, Object> properties, String prevID, Boolean allowConflict)
        {
            string newId = null;

            if (properties != null && properties.ContainsKey("_id"))
            {
                newId = (string)properties.Get("_id");
            }
            if (newId != null && !newId.Equals(Id, StringComparison.InvariantCultureIgnoreCase))
            {
                Log.W(Database.Tag, String.Format("Trying to put wrong _id to this: {0} properties: {1}", this, properties)); // TODO: Make sure all string formats use .NET codes, and not Java.
            }

            // Process _attachments dict, converting CBLAttachments to dicts:
            IDictionary <string, object> attachments = null;

            if (properties != null && properties.ContainsKey("_attachments"))
            {
                attachments = (IDictionary <string, object>)properties.Get("_attachments");
            }
            if (attachments != null && attachments.Count > 0)
            {
                var updatedAttachments = Attachment.InstallAttachmentBodies(attachments, Database);
                properties["_attachments"] = updatedAttachments;
            }

            var hasTrueDeletedProperty = false;

            if (properties != null)
            {
                hasTrueDeletedProperty = properties.Get("_deleted") != null && ((bool)properties.Get("_deleted"));
            }

            var deleted = (properties == null) || hasTrueDeletedProperty;
            var rev     = new RevisionInternal(Id, null, deleted, Database);

            if (deleted)
            {
                rev.SetJson(Encoding.UTF8.GetBytes("{}"));
            }
            if (properties != null)
            {
                rev.SetProperties(properties);
            }

            var newRev = Database.PutRevision(rev, prevID, allowConflict);

            if (newRev == null)
            {
                return(null);
            }
            return(new SavedRevision(this, newRev));
        }
Ejemplo n.º 4
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);
        }
Ejemplo n.º 5
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);
        }
Ejemplo n.º 6
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["message"] = "hi";
            rev.SetProperties(revProperties);
            database.ForceInsert(rev, revHistory, null);
        }
Ejemplo n.º 7
0
        internal SavedRevision PutProperties(IDictionary <string, object> properties, string
                                             prevID, bool allowConflict)
        {
            string newId = null;

            if (properties != null && properties.ContainsKey("_id"))
            {
                newId = (string)properties.Get("_id");
            }
            if (newId != null && !Sharpen.Runtime.EqualsIgnoreCase(newId, GetId()))
            {
                Log.W(Database.Tag, string.Format("Trying to put wrong _id to this: %s properties: %s"
                                                  , this, properties));
            }
            // Process _attachments dict, converting CBLAttachments to dicts:
            IDictionary <string, object> attachments = null;

            if (properties != null && properties.ContainsKey("_attachments"))
            {
                attachments = (IDictionary <string, object>)properties.Get("_attachments");
            }
            if (attachments != null && attachments.Count > 0)
            {
                IDictionary <string, object> updatedAttachments = Attachment.InstallAttachmentBodies
                                                                      (attachments, database);
                properties.Put("_attachments", updatedAttachments);
            }
            bool hasTrueDeletedProperty = false;

            if (properties != null)
            {
                hasTrueDeletedProperty = properties.Get("_deleted") != null && ((bool)properties.
                                                                                Get("_deleted"));
            }
            bool             deleted = (properties == null) || hasTrueDeletedProperty;
            RevisionInternal rev     = new RevisionInternal(documentId, null, deleted, database);

            if (properties != null)
            {
                rev.SetProperties(properties);
            }
            RevisionInternal newRev = database.PutRevision(rev, prevID, allowConflict);

            if (newRev == null)
            {
                return(null);
            }
            return(new SavedRevision(this, newRev));
        }
Ejemplo n.º 8
0
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
        public virtual void TestPulledChangesAreExternal()
        {
            changeNotifications = 0;
            Database.ChangeListener changeListener = new _ChangeListener_61(this);
            database.AddChangeListener(changeListener);
            // Insert a document as if it came from a remote source.
            RevisionInternal             rev        = new RevisionInternal("docId", "1-rev", false, database);
            IDictionary <string, object> properties = new Dictionary <string, object>();

            properties.Put("_id", rev.GetDocId());
            properties.Put("_rev", rev.GetRevId());
            rev.SetProperties(properties);
            database.ForceInsert(rev, Arrays.AsList(rev.GetRevId()), GetReplicationURL());
            // Make sure that the assertion in changeListener was called.
            NUnit.Framework.Assert.AreEqual(1, changeNotifications);
        }
Ejemplo n.º 9
0
        /// <summary>
        /// Adds an existing revision copied from another database.  Unlike a normal insertion, this does
        /// not assign a new revision ID; instead the revision's ID must be given.  Ths revision's history
        /// (ancestry) must be given, which can put it anywhere in the revision tree.  It's not an error if
        /// the revision already exists locally; it will just be ignored.
        ///
        /// This is not an operation that clients normall perform; it's used by the replicator.  You might want
        /// to use it if you're pre-loading a database with canned content, or if you're implementing some new
        /// kind of replicator that transfers revisions from another database
        /// </summary>
        /// <param name="properties">The properties of the revision (_id and _rev will be ignored but _deleted
        /// and _attachments are recognized)</param>
        /// <param name="attachments">A dictionary providing attachment bodies.  The keys are the attachment
        /// names (matching the keys in the properties `_attachments` dictionary) and the values are streams
        /// that contain the attachment bodies.</param>
        /// <param name="revisionHistory">The revision history in the form of an array of revision-ID strings, in
        /// reverse chronological order.  The first item must be the new revision's ID.  Following items are its
        /// parent's ID, etc.</param>
        /// <param name="sourceUri">The URL of the database this revision came from, if any.  (This value
        /// shows up in the Database Changed event triggered by this insertion, and can help clients decide
        /// whether the change is local or not)</param>
        /// <returns><c>true</c> on success, false otherwise</returns>
        public bool PutExistingRevision(IDictionary <string, object> properties, IDictionary <string, Stream> attachments, IList <string> revisionHistory, Uri sourceUri)
        {
            if (revisionHistory == null || revisionHistory.Count == 0)
            {
                Log.To.Database.E(Tag, "Invalid revision history in PutExistingRevision (must contain at " +
                                  "least one revision ID), throwing...");
                throw new ArgumentException("revisionHistory");
            }

            var revIDs = revisionHistory.AsRevIDs().ToList();
            var rev    = new RevisionInternal(Id, revIDs[0], properties.CblDeleted());

            rev.SetProperties(PropertiesToInsert(properties));
            if (!Database.RegisterAttachmentBodies(attachments, rev))
            {
                Log.To.Database.W(Tag, "Failed to register attachment bodies, aborting insert...");
                return(false);
            }

            Database.ForceInsert(rev, revIDs, sourceUri);
            return(true);
        }
Ejemplo n.º 10
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);
        }
Ejemplo n.º 11
0
        public void TestRevTreeChangeNotification()
        {
            const string DOCUMENT_ID = "MyDocId";

            var rev           = new RevisionInternal(DOCUMENT_ID, "1-one", false, database);
            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-three", false, database);
            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-two");
            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 ex)
                {
                    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-winner", false, database);
            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-two");
            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 ex)
                {
                    Assert.Fail();
                }
            };

            database.Changed += handler;
            database.ForceInsert(conflictRev, conflictRevHistory, null);
            database.Changed -= handler;
        }
Ejemplo n.º 12
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);
        }
Ejemplo n.º 13
0
        /// <summary>Fetches the contents of a revision from the remote db, including its parent revision ID.
        ///     </summary>
        /// <remarks>
        /// Fetches the contents of a revision from the remote db, including its parent revision ID.
        /// The contents are stored into rev.properties.
        /// </remarks>
        internal void PullRemoteRevision(RevisionInternal rev)
        {
            Log.D(Tag, this + "|" + Thread.CurrentThread() + ": pullRemoteRevision with rev: " + rev);
            Log.D(Tag, this + "|" + Thread.CurrentThread() + ": pullRemoteRevision() calling asyncTaskStarted()");

            AsyncTaskStarted();

            httpConnectionCount++;

            // Construct a query. We want the revision history, and the bodies of attachments that have
            // been added since the latest revisions we have locally.
            // See: http://wiki.apache.org/couchdb/HTTP_Document_API#Getting_Attachments_With_a_Document
            var path      = new StringBuilder("/" + HttpUtility.UrlEncode(rev.GetDocId()) + "?rev=" + HttpUtility.UrlEncode(rev.GetRevId()) + "&revs=true&attachments=true");
            var knownRevs = KnownCurrentRevIDs(rev);

            if (knownRevs == null)
            {
                //this means something is wrong, possibly the replicator has shut down
                Log.D(Tag, this + "|" + Thread.CurrentThread() + ": pullRemoteRevision() calling asyncTaskFinished()");
                AsyncTaskFinished(1);
                httpConnectionCount--;
                return;
            }

            if (knownRevs.Count > 0)
            {
                path.Append("&atts_since=");
                path.Append(JoinQuotedEscaped(knownRevs));
            }

            //create a final version of this variable for the log statement inside
            //FIXME find a way to avoid this
            var pathInside = path.ToString();

            SendAsyncMultipartDownloaderRequest(HttpMethod.Get, pathInside, null, LocalDatabase, (result, e) =>
            {
                try
                {
                    // OK, now we've got the response revision:
                    Log.D(Tag, this + ": pullRemoteRevision got response for rev: " + rev);
                    if (result != null)
                    {
                        var properties = ((JObject)result).ToObject <IDictionary <string, object> >();
                        var history    = Database.ParseCouchDBRevisionHistory(properties);

                        if (history != null)
                        {
                            rev.SetProperties(properties);
                            // Add to batcher ... eventually it will be fed to -insertRevisions:.
                            var toInsert = new AList <object> ();
                            toInsert.AddItem(rev);
                            toInsert.AddItem(history);
                            Log.D(Tag, this + ": pullRemoteRevision add rev: " + rev + " to batcher");
                            downloadsToInsert.QueueObject(toInsert);
                            Log.D(Tag, this + "|" + Thread.CurrentThread() + ": pullRemoteRevision.onCompletion() calling asyncTaskStarted()");
                            AsyncTaskStarted();
                        }
                        else
                        {
                            Log.W(Tag, this + ": Missing revision history in response from " + pathInside);
                            CompletedChangesCount += 1;
                        }
                    }
                    else
                    {
                        if (e != null)
                        {
                            Log.E(Tag, "Error pulling remote revision", e);
                            LastError = e;
                        }
                        CompletedChangesCount += 1;
                    }
                }
                finally
                {
                    Log.D(Tag, this + "|" + Thread.CurrentThread() + ": pullRemoteRevision.onCompletion() calling asyncTaskFinished()");
                    AsyncTaskFinished(1);
                }

                // Note that we've finished this task; then start another one if there
                // are still revisions waiting to be pulled:
                --httpConnectionCount;
                PullRemoteRevisions();
            });
        }
Ejemplo n.º 14
0
        public void TestRevTree()
        {
            var rev           = new RevisionInternal("MyDocId", "4-abcd", false);
            var revProperties = new Dictionary <string, object>();

            revProperties["_id"]     = rev.DocID;
            revProperties["_rev"]    = rev.RevID;
            revProperties["message"] = "hi";
            rev.SetProperties(revProperties);

            var revHistory = new List <string>();

            revHistory.Add(rev.RevID);
            revHistory.Add("3-abcd");
            revHistory.Add("2-abcd");
            revHistory.Add("1-abcd");
            database.ForceInsert(rev, revHistory, null);
            Assert.AreEqual(1, database.GetDocumentCount());

            VerifyHistory(database, rev, revHistory);
            var conflict           = new RevisionInternal("MyDocId", "5-abcd", false);
            var conflictProperties = new Dictionary <string, object>();

            conflictProperties["_id"]     = conflict.DocID;
            conflictProperties["_rev"]    = conflict.RevID;
            conflictProperties["message"] = "yo";
            conflict.SetProperties(conflictProperties);

            var conflictHistory = new List <string>();

            conflictHistory.Add(conflict.RevID);
            conflictHistory.Add("4-bcde");
            conflictHistory.Add("3-bcde");
            conflictHistory.Add("2-abcd");
            conflictHistory.Add("1-abcd");
            database.ForceInsert(conflict, conflictHistory, null);
            Assert.AreEqual(1, database.GetDocumentCount());
            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.Add(other.RevID);
            database.ForceInsert(other, otherHistory, null);

            // Fetch one of those phantom revisions with no body:
            var rev2 = database.GetDocument(rev.DocID, "2-abcd",
                                            true);

            Assert.IsNull(rev2);

            // Make sure no duplicate rows were inserted for the common revisions:
            Assert.IsTrue(database.GetLastSequenceNumber() <= 8);
            // Make sure the revision with the higher revID wins the conflict:
            var current = database.GetDocument(rev.DocID, 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.Add(conflict);
            expectedChanges.Add(other);
            Assert.AreEqual(expectedChanges, changes);
            options.IncludeConflicts = true;
            changes         = database.ChangesSince(0, options, null, null);
            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));
        }
        public void TestValidations()
        {
            ValidateDelegate validator = (newRevision, context) =>
            {
                Assert.IsNotNull(newRevision);
                Assert.IsNotNull(context);
                Assert.IsTrue(newRevision.Properties != null || newRevision.IsDeletion);

                validationCalled = true;

                bool hoopy = newRevision.IsDeletion || (newRevision.Properties.Get("towel") != null);
                Log.V(ValidationsTest.Tag, string.Format("--- Validating {0} --> {1}", 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);
            Assert.IsTrue(validationCalled);
            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);
            Assert.IsTrue(validationCalled);
            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);
            }
            Assert.IsTrue(validationCalled);
            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);
            }
            Assert.IsTrue(validationCalled);
            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);
            Assert.IsTrue(validationCalled);
            Assert.AreEqual("ford", rev.GetDocId());

            // DELETE a document:
            rev = new RevisionInternal(rev.GetDocId(), rev.GetRevId(), true, database);
            Assert.IsTrue(rev.IsDeleted());
            validationCalled = false;
            rev = database.PutRevision(rev, rev.GetRevId(), false, status);
            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);
            }
            Assert.IsTrue(validationCalled);
            Assert.IsTrue(gotExpectedError);
        }
Ejemplo n.º 16
0
        public void TestViewIndex()
        {
            int numTimesMapFunctionInvoked = 0;
            var dict1 = new Dictionary <string, object>();

            dict1["key"] = "one";

            var dict2 = new Dictionary <string, object>();

            dict2["key"] = "two";

            var dict3 = new Dictionary <string, object>();

            dict3["key"] = "three";

            var dictX = new Dictionary <string, object>();

            dictX["clef"] = "quatre";

            var rev1 = PutDoc(database, dict1);
            var rev2 = PutDoc(database, dict2);
            var rev3 = PutDoc(database, dict3);

            PutDoc(database, dictX);

            var view            = database.GetView("aview");
            var numTimesInvoked = 0;

            MapDelegate mapBlock = (document, emitter) =>
            {
                numTimesInvoked += 1;

                Assert.IsNotNull(document["_id"]);
                Assert.IsNotNull(document["_rev"]);

                if (document.ContainsKey("key") && document["key"] != null)
                {
                    emitter(document["key"], null);
                }
            };

            view.SetMap(mapBlock, "1");

            Assert.AreEqual(1, view.Id);
            Assert.IsTrue(view.IsStale);
            view.UpdateIndex();

            IList <IDictionary <string, object> > dumpResult = view.Dump();

            Log.V(Tag, "View dump: " + dumpResult);
            Assert.AreEqual(3, dumpResult.Count);
            Assert.AreEqual("\"one\"", dumpResult[0]["key"]);
            Assert.AreEqual(1, dumpResult[0]["seq"]);
            Assert.AreEqual("\"two\"", dumpResult[2]["key"]);
            Assert.AreEqual(2, dumpResult[2]["seq"]);
            Assert.AreEqual("\"three\"", dumpResult[1]["key"]);
            Assert.AreEqual(3, dumpResult[1]["seq"]);

            //no-op reindex
            Assert.IsFalse(view.IsStale);
            view.UpdateIndex();

            // Now add a doc and update a doc:
            var threeUpdated = new RevisionInternal(rev3.GetDocId(), rev3.GetRevId(), false, database);

            numTimesMapFunctionInvoked = numTimesInvoked;

            var newdict3 = new Dictionary <string, object>();

            newdict3["key"] = "3hree";
            threeUpdated.SetProperties(newdict3);

            Status status = new Status();

            rev3 = database.PutRevision(threeUpdated, rev3.GetRevId(), false, status);
            Assert.IsTrue(status.IsSuccessful);

            // Reindex again:
            Assert.IsTrue(view.IsStale);
            view.UpdateIndex();

            // Make sure the map function was only invoked one more time (for the document that was added)
            Assert.AreEqual(numTimesInvoked, numTimesMapFunctionInvoked + 1);

            var dict4 = new Dictionary <string, object>();

            dict4["key"] = "four";
            var rev4       = PutDoc(database, dict4);
            var twoDeleted = new RevisionInternal(rev2.GetDocId(), rev2.GetRevId(), true, database);

            database.PutRevision(twoDeleted, rev2.GetRevId(), false, status);
            Assert.IsTrue(status.IsSuccessful);

            // Reindex again:
            Assert.IsTrue(view.IsStale);
            view.UpdateIndex();
            dumpResult = view.Dump();
            Log.V(Tag, "View dump: " + dumpResult);
            Assert.AreEqual(3, dumpResult.Count);
            Assert.AreEqual("\"one\"", dumpResult[2]["key"]);
            Assert.AreEqual(1, dumpResult[2]["seq"]);
            Assert.AreEqual("\"3hree\"", dumpResult[0]["key"]);
            Assert.AreEqual(5, dumpResult[0]["seq"]);
            Assert.AreEqual("\"four\"", dumpResult[1]["key"]);
            Assert.AreEqual(6, dumpResult[1]["seq"]);

            // Now do a real query:
            IList <QueryRow> rows = view.QueryWithOptions(null).ToList();

            Assert.AreEqual(3, rows.Count);
            Assert.AreEqual("one", rows[2].Key);
            Assert.AreEqual(rev1.GetDocId(), rows[2].DocumentId);
            Assert.AreEqual("3hree", rows[0].Key);
            Assert.AreEqual(rev3.GetDocId(), rows[0].DocumentId);
            Assert.AreEqual("four", rows[1].Key);
            Assert.AreEqual(rev4.GetDocId(), rows[1].DocumentId);
            view.DeleteIndex();
        }
Ejemplo n.º 17
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["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.DocumentCount);
            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["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");
            database.ForceInsert(conflict, conflictHistory, null);
            NUnit.Framework.Assert.AreEqual(1, database.DocumentCount);
            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["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 <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 <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);
        }
Ejemplo n.º 18
0
        public void TestValidations()
        {
            ValidateDelegate validator = (newRevision, context) =>
            {
                Assert.IsNotNull(newRevision);
                Assert.IsNotNull(context);
                Assert.IsTrue(newRevision.Properties != null || newRevision.IsDeletion);

                validationCalled = true;

                bool hoopy = newRevision.IsDeletion || (newRevision.Properties.Get("towel") != null);
                Console.WriteLine("--- Validating {0} --> {1}", 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);

            validationCalled = false;
            rev = database.PutRevision(rev, null, false);
            Assert.IsTrue(validationCalled);

            // PUT a valid update:
            props["head_count"] = 3;
            rev.SetProperties(props);
            validationCalled = false;
            rev = database.PutRevision(rev, rev.RevID, false);
            Assert.IsTrue(validationCalled);

            // PUT an invalid update:
            props.Remove("towel");
            rev.SetProperties(props);
            validationCalled = false;
            Assert.Throws <CouchbaseLiteException>(() => rev = database.PutRevision(rev, rev.RevID, false));
            Assert.IsTrue(validationCalled);

            // POST an invalid new document:
            props            = new Dictionary <string, object>();
            props["name"]    = "Vogon";
            props["poetry"]  = true;
            rev              = new RevisionInternal(props);
            validationCalled = false;
            Assert.Throws <CouchbaseLiteException>(() => database.PutRevision(rev, null, false));
            Assert.IsTrue(validationCalled);

            // 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);
            validationCalled = false;
            rev              = database.PutRevision(rev, null, false);
            Assert.IsTrue(validationCalled);
            Assert.AreEqual("ford", rev.DocID);

            // DELETE a document:
            rev = new RevisionInternal(rev.DocID, rev.RevID, true);
            Assert.IsTrue(rev.Deleted);
            validationCalled = false;
            rev = database.PutRevision(rev, rev.RevID, false);
            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);
            validationCalled = false;
            Assert.Throws <CouchbaseLiteException>(() => rev = database.PutRevision(rev, null, false));
            Assert.IsTrue(validationCalled);

            // Cancel the validation
            database.SetValidation("hoopy", null);
            validationCalled = false;
            Assert.DoesNotThrow(() => rev = database.PutRevision(rev, null, false));
            Assert.IsFalse(validationCalled);
        }
Ejemplo n.º 19
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);

            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));
        }
Ejemplo n.º 20
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);
        }
Ejemplo n.º 21
0
        /// <summary>
        /// Attempt to update a document based on the information in the HTTP request
        /// </summary>
        /// <returns>The resulting status of the operation</returns>
        /// <param name="context">The request context</param>
        /// <param name="db">The database in which the document exists</param>
        /// <param name="docId">The ID of the document being updated</param>
        /// <param name="body">The new document body</param>
        /// <param name="deleting">Whether or not the document is being deleted</param>
        /// <param name="allowConflict">Whether or not to allow a conflict to be inserted</param>
        /// <param name="outRev">The resulting revision of the document</param>
        public static StatusCode UpdateDocument(ICouchbaseListenerContext context, Database db, string docId, Body body, bool deleting,
                                                bool allowConflict, out RevisionInternal outRev)
        {
            outRev = null;
            if (body != null && !body.IsValidJSON())
            {
                return(StatusCode.BadJson);
            }

            string prevRevId;

            if (!deleting)
            {
                var properties = body.GetProperties();
                deleting = properties.GetCast <bool>("_deleted");
                if (docId == null)
                {
                    // POST's doc ID may come from the _id field of the JSON body.
                    docId = properties.CblID();
                    if (docId == null && deleting)
                    {
                        return(StatusCode.BadId);
                    }
                }

                // PUT's revision ID comes from the JSON body.
                prevRevId = properties.GetCast <string>("_rev");
            }
            else
            {
                // DELETE's revision ID comes from the ?rev= query param
                prevRevId = context.GetQueryParam("rev");
            }

            // A backup source of revision ID is an If-Match header:
            if (prevRevId == null)
            {
                prevRevId = context.IfMatch();
            }

            if (docId == null && deleting)
            {
                return(StatusCode.BadId);
            }

            RevisionInternal rev = new RevisionInternal(docId, null, deleting);

            rev.SetBody(body);

            // Check for doc expiration
            var expirationTime = default(DateTime?);
            var tmp            = default(object);
            var props          = rev.GetProperties();
            var hasValue       = false;

            if (props != null && props.TryGetValue("_exp", out tmp))
            {
                hasValue = true;
                if (tmp != null)
                {
                    if (tmp is DateTime || tmp is DateTimeOffset)
                    {
                        expirationTime = (DateTime)tmp;
                    }
                    else
                    {
                        try {
                            expirationTime = Convert.ToDateTime(tmp);
                        } catch (Exception) {
                            try {
                                var num = Convert.ToInt64(tmp);
                                expirationTime = Misc.OffsetFromEpoch(TimeSpan.FromSeconds(num));
                            } catch (Exception) {
                                Log.To.Router.E(TAG, "Invalid value for _exp: {0}", tmp);
                                return(StatusCode.BadRequest);
                            }
                        }
                    }
                }

                props.Remove("_exp");
                rev.SetProperties(props);
            }

            var        castContext = context as ICouchbaseListenerContext2;
            var        source      = castContext != null && !castContext.IsLoopbackRequest ? castContext.Sender : null;
            StatusCode status      = deleting ? StatusCode.Ok : StatusCode.Created;

            try {
                if (docId != null && docId.StartsWith("_local"))
                {
                    if (expirationTime.HasValue)
                    {
                        return(StatusCode.BadRequest);
                    }

                    Log.To.Router.I(TAG, "Attempting to insert local {0} on top of {1} from PUT request", rev, prevRevId != null ? prevRevId.ToString() : "<root>");
                    outRev = db.Storage.PutLocalRevision(rev, prevRevId.AsRevID(), true); //TODO: Doesn't match iOS
                }
                else
                {
                    Log.To.Router.I(TAG, "Attempting to insert {0} on top of {1} from PUT request", rev, prevRevId != null ? prevRevId.ToString() : "<root>");
                    outRev = db.PutRevision(rev, prevRevId.AsRevID(), allowConflict, source);
                    if (hasValue)
                    {
                        db.Storage?.SetDocumentExpiration(rev.DocID, expirationTime);
                    }
                }
            } catch (CouchbaseLiteException e) {
                status = e.Code;
            }

            return(status);
        }
Ejemplo n.º 22
0
 public void OnCompletion(object response, Exception e)
 {
     try
     {
         Log.V(Log.TagSync, "%s: got /_revs_diff response");
         IDictionary <string, object> results = (IDictionary <string, object>)response;
         if (e != null)
         {
             this._enclosing.SetError(e);
             this._enclosing.RevisionFailed();
         }
         else
         {
             if (results.Count != 0)
             {
                 // Go through the list of local changes again, selecting the ones the destination server
                 // said were missing and mapping them to a JSON dictionary in the form _bulk_docs wants:
                 IList <object> docsToSend = new AList <object>();
                 RevisionList   revsToSend = new RevisionList();
                 foreach (RevisionInternal rev in changes)
                 {
                     // Is this revision in the server's 'missing' list?
                     IDictionary <string, object> properties = null;
                     IDictionary <string, object> revResults = (IDictionary <string, object>)results.Get
                                                                   (rev.GetDocId());
                     if (revResults == null)
                     {
                         continue;
                     }
                     IList <string> revs = (IList <string>)revResults.Get("missing");
                     if (revs == null || !revs.Contains(rev.GetRevId()))
                     {
                         this._enclosing.RemovePending(rev);
                         continue;
                     }
                     // Get the revision's properties:
                     EnumSet <Database.TDContentOptions> contentOptions = EnumSet.Of(Database.TDContentOptions
                                                                                     .TDIncludeAttachments);
                     if (!this._enclosing.dontSendMultipart && this._enclosing.revisionBodyTransformationBlock
                         == null)
                     {
                         contentOptions.AddItem(Database.TDContentOptions.TDBigAttachmentsFollow);
                     }
                     RevisionInternal loadedRev;
                     try
                     {
                         loadedRev  = this._enclosing.db.LoadRevisionBody(rev, contentOptions);
                         properties = new Dictionary <string, object>(rev.GetProperties());
                     }
                     catch (CouchbaseLiteException)
                     {
                         Log.W(Log.TagSync, "%s Couldn't get local contents of %s", rev, this._enclosing);
                         this._enclosing.RevisionFailed();
                         continue;
                     }
                     RevisionInternal populatedRev      = this._enclosing.TransformRevision(loadedRev);
                     IList <string>   possibleAncestors = (IList <string>)revResults.Get("possible_ancestors"
                                                                                         );
                     properties = new Dictionary <string, object>(populatedRev.GetProperties());
                     IDictionary <string, object> revisions = this._enclosing.db.GetRevisionHistoryDictStartingFromAnyAncestor
                                                                  (populatedRev, possibleAncestors);
                     properties.Put("_revisions", revisions);
                     populatedRev.SetProperties(properties);
                     // Strip any attachments already known to the target db:
                     if (properties.ContainsKey("_attachments"))
                     {
                         // Look for the latest common ancestor and stub out older attachments:
                         int minRevPos = Couchbase.Lite.Replicator.Pusher.FindCommonAncestor(populatedRev,
                                                                                             possibleAncestors);
                         Database.StubOutAttachmentsInRevBeforeRevPos(populatedRev, minRevPos + 1, false);
                         properties = populatedRev.GetProperties();
                         if (!this._enclosing.dontSendMultipart && this._enclosing.UploadMultipartRevision
                                 (populatedRev))
                         {
                             continue;
                         }
                     }
                     if (properties == null || !properties.ContainsKey("_id"))
                     {
                         throw new InvalidOperationException("properties must contain a document _id");
                     }
                     revsToSend.AddItem(rev);
                     docsToSend.AddItem(properties);
                 }
                 //TODO: port this code from iOS
                 // Post the revisions to the destination:
                 this._enclosing.UploadBulkDocs(docsToSend, revsToSend);
             }
             else
             {
                 // None of the revisions are new to the remote
                 foreach (RevisionInternal revisionInternal in changes)
                 {
                     this._enclosing.RemovePending(revisionInternal);
                 }
             }
         }
     }
     finally
     {
         Log.V(Log.TagSync, "%s | %s: processInbox.sendAsyncRequest() calling asyncTaskFinished()"
               , this, Sharpen.Thread.CurrentThread());
         this._enclosing.AsyncTaskFinished(1);
     }
 }