/// <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; } }
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(); } }
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); }
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; } }
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); }