/// <summary>
        /// Subscribes this object to the given database's <c>Changed</c> event for
        /// processing
        /// </summary>
        /// <param name="db">Db.</param>
        public void SubscribeToDatabase(Database db, long since, ChangesOptions options)
        {
            if (db == null)
            {
                return;
            }


            Db       = db;
            IsAsync  = true;
            _since   = since;
            _options = options;
            if (ChangesFeedMode == ChangesFeedMode.LongPoll)
            {
                var currentChanges = Db.ChangesSince(since, options, ChangesFilter, FilterParams);
                IsAsync = !currentChanges.Any();
                if (!IsAsync)
                {
                    if (ChangesIncludeConflicts)
                    {
                        Response.JsonBody = new Body(DatabaseMethods.ResponseBodyForChanges(currentChanges, since, options.Limit, this));
                    }
                    else
                    {
                        Response.JsonBody = new Body(DatabaseMethods.ResponseBodyForChanges(currentChanges, since, this));
                    }
                }
                else
                {
                    Db.Changed += DatabaseChanged;
                }
            }
        }
        internal override void BeginReplicating()
        {
            Log.D(Tag, "beginReplicating() called");

            // If we're still waiting to create the remote db, do nothing now. (This method will be
            // re-invoked after that request finishes; see maybeCreateRemoteDB() above.)
            if (creatingTarget)
            {
                Log.D(Tag, "creatingTarget == true, doing nothing");
                return;
            }

            pendingSequences = new SortedSet <long>();
            try
            {
                maxPendingSequence = Int64.Parse(LastSequence);
            }
            catch (Exception e)
            {
                Log.W(Tag, "Error converting lastSequence: " + LastSequence + " to long. Using 0");
                maxPendingSequence = 0;
            }

            if (Filter != null)
            {
                filter = LocalDatabase.GetFilter(Filter);
            }

            if (Filter != null && filter == null)
            {
                Log.W(Tag, string.Format("{0}: No ReplicationFilter registered for filter '{1}'; ignoring"
                                         , this, Filter));
            }

            // Process existing changes since the last push:
            long lastSequenceLong = 0;

            if (LastSequence != null)
            {
                lastSequenceLong = long.Parse(LastSequence);
            }

            var options = new ChangesOptions();

            options.SetIncludeConflicts(true);
            var changes = LocalDatabase.ChangesSince(lastSequenceLong, options, filter);

            if (changes.Count > 0)
            {
                Batcher.QueueObjects(changes);
                Batcher.Flush();
            }

            // Now listen for future changes (in continuous mode):
            if (continuous)
            {
                observing              = true;
                LocalDatabase.Changed += OnChanged;
            }
        }
Beispiel #3
0
        public override void BeginReplicating()
        {
            Log.D(Log.TagSync, "%s: beginReplicating() called", this);
            // If we're still waiting to create the remote db, do nothing now. (This method will be
            // re-invoked after that request finishes; see maybeCreateRemoteDB() above.)
            if (creatingTarget)
            {
                Log.D(Log.TagSync, "%s: creatingTarget == true, doing nothing", this);
                return;
            }
            pendingSequences = Sharpen.Collections.SynchronizedSortedSet(new TreeSet <long>());
            try
            {
                maxPendingSequence = long.Parse(lastSequence);
            }
            catch (FormatException)
            {
                Log.W(Log.TagSync, "Error converting lastSequence: %s to long.  Using 0", lastSequence
                      );
                maxPendingSequence = System.Convert.ToInt64(0);
            }
            if (filterName != null)
            {
                filter = db.GetFilter(filterName);
            }
            if (filterName != null && filter == null)
            {
                Log.W(Log.TagSync, "%s: No ReplicationFilter registered for filter '%s'; ignoring"
                      , this, filterName);
            }
            // Process existing changes since the last push:
            long lastSequenceLong = 0;

            if (lastSequence != null)
            {
                lastSequenceLong = long.Parse(lastSequence);
            }
            ChangesOptions options = new ChangesOptions();

            options.SetIncludeConflicts(true);
            RevisionList changes = db.ChangesSince(lastSequenceLong, options, filter);

            if (changes.Count > 0)
            {
                batcher.QueueObjects(changes);
                batcher.Flush();
            }
            // Now listen for future changes (in continuous mode):
            if (continuous)
            {
                observing = true;
                db.AddChangeListener(this);
            }
        }
        public override void BeginReplicating()
        {
            // If we're still waiting to create the remote db, do nothing now. (This method will be
            // re-invoked after that request finishes; see maybeCreateRemoteDB() above.)
            Log.D(Database.Tag, this + "|" + Sharpen.Thread.CurrentThread() + ": beginReplicating() called"
                  );
            if (creatingTarget)
            {
                Log.D(Database.Tag, this + "|" + Sharpen.Thread.CurrentThread() + ": creatingTarget == true, doing nothing"
                      );
                return;
            }
            else
            {
                Log.D(Database.Tag, this + "|" + Sharpen.Thread.CurrentThread() + ": creatingTarget != true, continuing"
                      );
            }
            if (filterName != null)
            {
                filter = db.GetFilter(filterName);
            }
            if (filterName != null && filter == null)
            {
                Log.W(Database.Tag, string.Format("%s: No ReplicationFilter registered for filter '%s'; ignoring"
                                                  , this, filterName));
            }
            // Process existing changes since the last push:
            long lastSequenceLong = 0;

            if (lastSequence != null)
            {
                lastSequenceLong = long.Parse(lastSequence);
            }
            ChangesOptions options = new ChangesOptions();

            options.SetIncludeConflicts(true);
            RevisionList changes = db.ChangesSince(lastSequenceLong, options, filter);

            if (changes.Count > 0)
            {
                batcher.QueueObjects(changes);
                batcher.Flush();
            }
            // Now listen for future changes (in continuous mode):
            if (continuous)
            {
                observing = true;
                db.AddChangeListener(this);
                Log.D(Database.Tag, this + "|" + Sharpen.Thread.CurrentThread() + ": pusher.beginReplicating() calling asyncTaskStarted()"
                      );
                AsyncTaskStarted();
            }
        }
Beispiel #5
0
        public RevisionList ChangesSince(Int64 lastSequence, ChangesOptions options, RevisionFilter filter)
        {
            // http://wiki.apache.org/couchdb/HTTP_database_API#Changes
            // Translate options to ForestDB:
            if (options.Descending)
            {
                // https://github.com/couchbase/couchbase-lite-ios/issues/641
                throw new CouchbaseLiteException(StatusCode.NotImplemented);
            }

            var forestOps = C4EnumeratorOptions.DEFAULT;

            forestOps.flags |= C4EnumeratorFlags.IncludeDeleted | C4EnumeratorFlags.IncludeNonConflicted;
            if (options.IncludeDocs || options.IncludeConflicts || filter != null)
            {
                forestOps.flags |= C4EnumeratorFlags.IncludeBodies;
            }

            var changes = new RevisionList();
            var e       = new CBForestDocEnumerator(Forest, lastSequence, forestOps);

            foreach (var next in e)
            {
                var revs = default(IEnumerable <RevisionInternal>);
                if (options.IncludeConflicts)
                {
                    using (var enumerator = new CBForestHistoryEnumerator(next.GetDocument(), true, false)) {
                        var includeBody = forestOps.flags.HasFlag(C4EnumeratorFlags.IncludeBodies);
                        revs = enumerator.Select(x => new RevisionInternal(x.GetDocument(), includeBody)).ToList();
                    }
                }
                else
                {
                    revs = new List <RevisionInternal> {
                        new RevisionInternal(next.GetDocument(), forestOps.flags.HasFlag(C4EnumeratorFlags.IncludeBodies))
                    };
                }

                foreach (var rev in revs)
                {
                    Debug.Assert(rev != null);
                    if (filter == null || filter(rev))
                    {
                        if (!options.IncludeDocs)
                        {
                            rev.SetBody(null);
                        }

                        if (filter == null || filter(rev))
                        {
                            changes.Add(rev);
                        }
                    }
                }
            }

            if (options.SortBySequence)
            {
                changes.SortBySequence(!options.Descending);
                changes.Limit(options.Limit);
            }

            return(changes);
        }
        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));
        }
		/// <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);
		}
        internal override void BeginReplicating()
        {
            Log.D(TAG, "beginReplicating() called");

            // If we're still waiting to create the remote db, do nothing now. (This method will be
            // re-invoked after that request finishes; see maybeCreateRemoteDB() above.)
            if (_creatingTarget)
            {
                Log.D(TAG, "creatingTarget == true, doing nothing");
                return;
            }

            _pendingSequences = new SortedDictionary <long, int>();
            try {
                _maxPendingSequence = Int64.Parse(LastSequence);
            } catch (Exception e) {
                Log.W(TAG, "Error converting lastSequence: " + LastSequence + " to long. Using 0", e);
                _maxPendingSequence = 0;
            }

            if (Filter != null)
            {
                _filter = LocalDatabase.GetFilter(Filter);
            }
            else
            {
                // If not filter function was provided, but DocIds were
                // specified, then only push the documents listed in the
                // DocIds property. It is assumed that if the users
                // specified both a filter name and doc ids that their
                // custom filter function will handle that. This is
                // consistent with the iOS behavior.
                if (DocIds != null && DocIds.Any())
                {
                    _filter = (rev, filterParams) => DocIds.Contains(rev.Document.Id);
                }
            }

            if (Filter != null && _filter == null)
            {
                Log.W(TAG, string.Format("{0}: No ReplicationFilter registered for filter '{1}'; ignoring", this, Filter));
            }

            // Process existing changes since the last push:
            long lastSequenceLong = 0;

            if (LastSequence != null)
            {
                lastSequenceLong = long.Parse(LastSequence);
            }

            // Now listen for future changes (in continuous mode):
            // Note:  This needs to happen before adding the observer
            // or else there is a race condition.
            // A document could be added between the call to
            // ChangesSince and adding the observer, which would result
            // in a document being skipped
            if (Continuous)
            {
                _observing             = true;
                LocalDatabase.Changed += OnChanged;
            }

            var options = new ChangesOptions();

            options.IncludeConflicts = true;
            var changes = LocalDatabase.ChangesSince(lastSequenceLong, options, _filter, FilterParams);

            if (changes.Count > 0)
            {
                Batcher.QueueObjects(changes);
                Batcher.Flush();
            }

            if (Continuous)
            {
                if (changes.Count == 0)
                {
                    FireTrigger(ReplicationTrigger.WaitingForChanges);
                }
            }
            else
            {
                if (changes.Count == 0)
                {
                    FireTrigger(ReplicationTrigger.StopGraceful);
                }
            }
        }
        internal RevisionList ChangesSince(long lastSeq, ChangesOptions options, FilterDelegate filter, IDictionary<string, object> filterParams)
        {
            RevisionFilter revFilter = null;
            if (filter != null) {
                revFilter = (rev => RunFilter(filter, filterParams, rev));
            }

            return Storage.ChangesSince(lastSeq, options, revFilter);
        }
Beispiel #10
0
        internal override void BeginReplicating()
        {
            Log.D(Tag, "beginReplicating() called");

            // If we're still waiting to create the remote db, do nothing now. (This method will be
            // re-invoked after that request finishes; see maybeCreateRemoteDB() above.)
            if (creatingTarget)
            {
                Log.D(Tag, "creatingTarget == true, doing nothing");
                return;
            }

            pendingSequences = new SortedDictionary <long, int>();
            try
            {
                maxPendingSequence = Int64.Parse(LastSequence);
            }
            catch (Exception e)
            {
                Log.W(Tag, "Error converting lastSequence: " + LastSequence + " to long. Using 0");
                maxPendingSequence = 0;
            }

            if (Filter != null)
            {
                filter = LocalDatabase.GetFilter(Filter);
            }
            else
            {
                // If not filter function was provided, but DocIds were
                // specified, then only push the documents listed in the
                // DocIds property. It is assumed that if the users
                // specified both a filter name and doc ids that their
                // custom filter function will handle that. This is
                // consistent with the iOS behavior.
                if (DocIds != null && DocIds.Any())
                {
                    filter = (rev, filterParams) => DocIds.Contains(rev.Document.Id);
                }
            }

            if (Filter != null && filter == null)
            {
                Log.W(Tag, string.Format("{0}: No ReplicationFilter registered for filter '{1}'; ignoring", this, Filter));
            }

            // Process existing changes since the last push:
            long lastSequenceLong = 0;

            if (LastSequence != null)
            {
                lastSequenceLong = long.Parse(LastSequence);
            }

            var options = new ChangesOptions();

            options.SetIncludeConflicts(true);
            var changes = LocalDatabase.ChangesSince(lastSequenceLong, options, filter);

            if (changes.Count > 0)
            {
                Batcher.QueueObjects(changes);
                Batcher.Flush();
            }

            // Now listen for future changes (in continuous mode):
            if (continuous)
            {
                observing              = true;
                LocalDatabase.Changed += OnChanged;
            }
        }
        /// <summary>
        /// Returns a sorted list of changes made to documents in the database, in time order of application.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/database/changes.html#get--db-_changes
        /// <remarks>
        public static ICouchbaseResponseState GetChanges(ICouchbaseListenerContext context)
        {
            DBMonitorCouchbaseResponseState responseState = new DBMonitorCouchbaseResponseState();

            var responseObject = PerformLogicWithDatabase(context, true, db =>
            {
                var response           = context.CreateResponse();
                responseState.Response = response;
                if (context.ChangesFeedMode < ChangesFeedMode.Continuous)
                {
                    if (context.CacheWithEtag(db.LastSequenceNumber.ToString()))
                    {
                        response.InternalStatus = StatusCode.NotModified;
                        return(response);
                    }
                }

                var options                      = new ChangesOptions();
                responseState.Db                 = db;
                responseState.ContentOptions     = context.ContentOptions;
                responseState.ChangesFeedMode    = context.ChangesFeedMode;
                responseState.ChangesIncludeDocs = context.GetQueryParam <bool>("include_docs", bool.TryParse, false);
                options.SetIncludeDocs(responseState.ChangesIncludeDocs);
                responseState.ChangesIncludeConflicts = context.GetQueryParam("style") == "all_docs";
                options.SetIncludeConflicts(responseState.ChangesIncludeConflicts);
                options.SetContentOptions(context.ContentOptions);
                options.SetSortBySequence(!options.IsIncludeConflicts());
                options.SetLimit(context.GetQueryParam <int>("limit", int.TryParse, options.GetLimit()));
                int since = context.GetQueryParam <int>("since", int.TryParse, 0);

                string filterName = context.GetQueryParam("filter");
                if (filterName != null)
                {
                    Status status = new Status();
                    responseState.ChangesFilter = db.GetFilter(filterName, status);
                    if (responseState.ChangesFilter == null)
                    {
                        return(context.CreateResponse(status.Code));
                    }

                    responseState.FilterParams = context.GetQueryParams();
                }


                RevisionList changes = db.ChangesSince(since, options, responseState.ChangesFilter, responseState.FilterParams);
                if ((context.ChangesFeedMode >= ChangesFeedMode.Continuous) ||
                    (context.ChangesFeedMode == ChangesFeedMode.LongPoll && changes.Count == 0))
                {
                    // Response is going to stay open (continuous, or hanging GET):
                    response.Chunked = true;
                    if (context.ChangesFeedMode == ChangesFeedMode.EventSource)
                    {
                        response["Content-Type"] = "text/event-stream; charset=utf-8";
                    }

                    if (context.ChangesFeedMode >= ChangesFeedMode.Continuous)
                    {
                        response.WriteHeaders();
                        foreach (var rev in changes)
                        {
                            response.SendContinuousLine(ChangesDictForRev(rev, responseState), context.ChangesFeedMode);
                        }
                    }

                    responseState.SubscribeToDatabase(db);
                    string heartbeatParam = context.GetQueryParam("heartbeat");
                    if (heartbeatParam != null)
                    {
                        int heartbeat;
                        if (!int.TryParse(heartbeatParam, out heartbeat) || heartbeat <= 0)
                        {
                            responseState.IsAsync = false;
                            return(context.CreateResponse(StatusCode.BadParam));
                        }

                        heartbeat = Math.Min(heartbeat, MIN_HEARTBEAT);
                        string heartbeatResponse = context.ChangesFeedMode == ChangesFeedMode.EventSource ? "\n\n" : "\r\n";
                        responseState.StartHeartbeat(heartbeatResponse, heartbeat);
                    }

                    return(context.CreateResponse());
                }
                else
                {
                    if (responseState.ChangesIncludeConflicts)
                    {
                        response.JsonBody = new Body(ResponseBodyForChanges(changes, since, options.GetLimit(), responseState));
                    }
                    else
                    {
                        response.JsonBody = new Body(ResponseBodyForChanges(changes, since, responseState));
                    }

                    return(response);
                }
            });

            responseState.Response = responseObject;
            return(responseState);
        }
        internal override void BeginReplicating()
        {
            Log.D(Tag, "beginReplicating() called");

            // If we're still waiting to create the remote db, do nothing now. (This method will be
            // re-invoked after that request finishes; see maybeCreateRemoteDB() above.)
            if (creatingTarget)
            {
                Log.D(Tag, "creatingTarget == true, doing nothing");
                return;
            }

            pendingSequences = new SortedSet<long>();
            try
            {
                maxPendingSequence = Int64.Parse(LastSequence);
            }
            catch (Exception e)
            {
                Log.W(Tag, "Error converting lastSequence: " + LastSequence + " to long. Using 0");
                maxPendingSequence = 0;
            }

            if (Filter != null)
            {
                filter = LocalDatabase.GetFilter(Filter);
            }

            if (Filter != null && filter == null)
            {
                Log.W(Tag, string.Format("{0}: No ReplicationFilter registered for filter '{1}'; ignoring"
                    , this, Filter));
            }

            // Process existing changes since the last push:
            long lastSequenceLong = 0;
            if (LastSequence != null)
            {
                lastSequenceLong = long.Parse(LastSequence);
            }

            var options = new ChangesOptions();
            options.SetIncludeConflicts(true);
            var changes = LocalDatabase.ChangesSince(lastSequenceLong, options, filter);
            if (changes.Count > 0)
            {
                Batcher.QueueObjects(changes);
                Batcher.Flush();
            }

            // Now listen for future changes (in continuous mode):
            if (continuous)
            {
                observing = true;
                LocalDatabase.Changed += OnChanged;
            }
        }
Beispiel #13
0
		public override void BeginReplicating()
		{
			// If we're still waiting to create the remote db, do nothing now. (This method will be
			// re-invoked after that request finishes; see maybeCreateRemoteDB() above.)
			Log.D(Database.Tag, this + "|" + Sharpen.Thread.CurrentThread() + ": beginReplicating() called"
				);
			if (creatingTarget)
			{
				Log.D(Database.Tag, this + "|" + Sharpen.Thread.CurrentThread() + ": creatingTarget == true, doing nothing"
					);
				return;
			}
			else
			{
				Log.D(Database.Tag, this + "|" + Sharpen.Thread.CurrentThread() + ": creatingTarget != true, continuing"
					);
			}
			if (filterName != null)
			{
				filter = db.GetFilter(filterName);
			}
			if (filterName != null && filter == null)
			{
				Log.W(Database.Tag, string.Format("%s: No ReplicationFilter registered for filter '%s'; ignoring"
					, this, filterName));
			}
			// Process existing changes since the last push:
			long lastSequenceLong = 0;
			if (lastSequence != null)
			{
				lastSequenceLong = long.Parse(lastSequence);
			}
			ChangesOptions options = new ChangesOptions();
			options.SetIncludeConflicts(true);
			RevisionList changes = db.ChangesSince(lastSequenceLong, options, filter);
			if (changes.Count > 0)
			{
				batcher.QueueObjects(changes);
				batcher.Flush();
			}
			// Now listen for future changes (in continuous mode):
			if (continuous)
			{
				observing = true;
				db.AddChangeListener(this);
				Log.D(Database.Tag, this + "|" + Sharpen.Thread.CurrentThread() + ": pusher.beginReplicating() calling asyncTaskStarted()"
					);
				AsyncTaskStarted();
			}
		}
 public override void BeginReplicating()
 {
     Log.D(Log.TagSync, "%s: beginReplicating() called", this);
     // If we're still waiting to create the remote db, do nothing now. (This method will be
     // re-invoked after that request finishes; see maybeCreateRemoteDB() above.)
     if (creatingTarget)
     {
         Log.D(Log.TagSync, "%s: creatingTarget == true, doing nothing", this);
         return;
     }
     pendingSequences = Sharpen.Collections.SynchronizedSortedSet(new TreeSet<long>());
     try
     {
         maxPendingSequence = long.Parse(lastSequence);
     }
     catch (FormatException)
     {
         Log.W(Log.TagSync, "Error converting lastSequence: %s to long.  Using 0", lastSequence
             );
         maxPendingSequence = System.Convert.ToInt64(0);
     }
     if (filterName != null)
     {
         filter = db.GetFilter(filterName);
     }
     if (filterName != null && filter == null)
     {
         Log.W(Log.TagSync, "%s: No ReplicationFilter registered for filter '%s'; ignoring"
             , this, filterName);
     }
     // Process existing changes since the last push:
     long lastSequenceLong = 0;
     if (lastSequence != null)
     {
         lastSequenceLong = long.Parse(lastSequence);
     }
     ChangesOptions options = new ChangesOptions();
     options.SetIncludeConflicts(true);
     RevisionList changes = db.ChangesSince(lastSequenceLong, options, filter);
     if (changes.Count > 0)
     {
         batcher.QueueObjects(changes);
         batcher.Flush();
     }
     // Now listen for future changes (in continuous mode):
     if (continuous)
     {
         observing = true;
         db.AddChangeListener(this);
     }
 }
        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));
        }
        internal RevisionList ChangesSince(long lastSeq, ChangesOptions options, FilterDelegate filter)
        {
            // http://wiki.apache.org/couchdb/HTTP_database_API#Changes
            if (options == null)
            {
                options = new ChangesOptions();
            }

            var includeDocs = options.IsIncludeDocs() || (filter != null);
            var additionalSelectColumns = string.Empty;

            if (includeDocs)
            {
                additionalSelectColumns = ", json";
            }

            var sql = "SELECT sequence, revs.doc_id, docid, revid, deleted" + additionalSelectColumns
                      + " FROM revs, docs " + "WHERE sequence > ? AND current=1 " + "AND revs.doc_id = docs.doc_id "
                      + "ORDER BY revs.doc_id, revid DESC";
            var args = lastSeq;

            Cursor cursor = null;
            RevisionList changes = null;

            try
            {
                cursor = StorageEngine.RawQuery(sql, CommandBehavior.SequentialAccess, args);
                cursor.MoveToNext();

                changes = new RevisionList();
                long lastDocId = 0;

                while (!cursor.IsAfterLast())
                {
                    if (!options.IsIncludeConflicts())
                    {
                        // Only count the first rev for a given doc (the rest will be losing conflicts):
                        var docNumericId = cursor.GetLong(1);
                        if (docNumericId == lastDocId)
                        {
                            cursor.MoveToNext();
                            continue;
                        }
                        lastDocId = docNumericId;
                    }

                    var sequence = cursor.GetLong(0);
                    var rev = new RevisionInternal(cursor.GetString(2), cursor.GetString(3), (cursor.GetInt(4) > 0), this);
                    rev.SetSequence(sequence);

                    if (includeDocs)
                    {
                        ExpandStoredJSONIntoRevisionWithAttachments(cursor.GetBlob(5), rev, options.GetContentOptions());
                    }
                    IDictionary<string, object> paramsFixMe = null;
                    // TODO: these should not be null
                    if (RunFilter(filter, paramsFixMe, rev))
                    {
                        changes.AddItem(rev);
                    }
                    cursor.MoveToNext();
                }
            }
            catch (SQLException e)
            {
                Log.E(Tag, "Error looking for changes", e);
            }
            finally
            {
                if (cursor != null)
                {
                    cursor.Close();
                }
            }
            if (options.IsSortBySequence())
            {
                changes.SortBySequence();
            }
            changes.Limit(options.GetLimit());
            return changes;
        }
        internal override void BeginReplicating()
        {
            Log.D(TAG, "beginReplicating() called");

            // If we're still waiting to create the remote db, do nothing now. (This method will be
            // re-invoked after that request finishes; see maybeCreateRemoteDB() above.)
            if (_creatingTarget) {
                Log.D(TAG, "creatingTarget == true, doing nothing");
                return;
            }

            _pendingSequences = new SortedDictionary<long, int>();
            try {
                _maxPendingSequence = Int64.Parse(LastSequence);
            } catch (Exception e) {
                Log.W(TAG, "Error converting lastSequence: " + LastSequence + " to long. Using 0", e);
                _maxPendingSequence = 0;
            }

            if (Filter != null) {
                _filter = LocalDatabase.GetFilter(Filter);
            } else {
                // If not filter function was provided, but DocIds were
                // specified, then only push the documents listed in the
                // DocIds property. It is assumed that if the users
                // specified both a filter name and doc ids that their
                // custom filter function will handle that. This is 
                // consistent with the iOS behavior.
                if (DocIds != null && DocIds.Any()) {
                    _filter = (rev, filterParams) => DocIds.Contains(rev.Document.Id);
                }
            }

            if (Filter != null && _filter == null) {
                Log.W(TAG, string.Format("{0}: No ReplicationFilter registered for filter '{1}'; ignoring", this, Filter));
            }

            // Process existing changes since the last push:
            long lastSequenceLong = 0;
            if (LastSequence != null) {
                lastSequenceLong = long.Parse(LastSequence);
            }

            // Now listen for future changes (in continuous mode):
            // Note:  This needs to happen before adding the observer
            // or else there is a race condition.  
            // A document could be added between the call to
            // ChangesSince and adding the observer, which would result
            // in a document being skipped
            if (Continuous) {
                _observing = true;
                LocalDatabase.Changed += OnChanged;
            } 

            var options = new ChangesOptions();
            options.SetIncludeConflicts(true);
            var changes = LocalDatabase.ChangesSince(lastSequenceLong, options, _filter, FilterParams);
            if (changes.Count > 0) {
                Batcher.QueueObjects(changes);
                Batcher.Flush();
            }

            if (Continuous) {
                if (changes.Count == 0) {
                    FireTrigger(ReplicationTrigger.WaitingForChanges);
                }
            } else {
                if(changes.Count == 0) {
                    FireTrigger(ReplicationTrigger.StopGraceful);
                }
            }
        }
        internal IEnumerable<QueryRow> GetAllDocs(QueryOptions options)
        {
            // For regular all-docs, let storage do it all:
            if (options == null || options.AllDocsMode != AllDocsMode.BySequence) {
                return Storage.GetAllDocs(options);
            }

            if (options.Descending) {
                throw new CouchbaseLiteException("Descending all docs not implemented", StatusCode.NotImplemented);
            }

            ChangesOptions changesOpts = new ChangesOptions();
            changesOpts.SetLimit(options.Limit);
            changesOpts.SetIncludeDocs(options.IncludeDocs);
            changesOpts.SetIncludeConflicts(true);
            changesOpts.SetSortBySequence(true);

            long startSeq = KeyToSequence(options.StartKey, 1);
            long endSeq = KeyToSequence(options.EndKey, long.MaxValue);
            if (!options.InclusiveStart) {
                ++startSeq;
            }

            if (!options.InclusiveEnd) {
                --endSeq;
            }

            long minSeq = startSeq, maxSeq = endSeq;
            if (minSeq > maxSeq) {
                return null; // empty result
            }

            RevisionList revs = Storage.ChangesSince(minSeq - 1, changesOpts, null);
            if (revs == null) {
                return null;
            }

            var result = new List<QueryRow>();
            var revEnum = options.Descending ? revs.Reverse<RevisionInternal>() : revs;
            foreach (var rev in revEnum) {
                long seq = rev.GetSequence();
                if (seq < minSeq || seq > maxSeq) {
                    break;
                }

                var value = new NonNullDictionary<string, object> {
                    { "rev", rev.GetRevId() },
                    { "deleted", rev.IsDeleted() ? (object)true : null }
                };
                result.Add(new QueryRow(rev.GetDocId(), seq, rev.GetDocId(), value, rev, null));
            }

            return result;
        }
        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);
        }