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 doc = next.Document; var revs = default(IEnumerable<RevisionInternal>); if (options.IncludeConflicts) { using (var enumerator = new CBForestHistoryEnumerator(doc, true, false)) { var includeBody = forestOps.flags.HasFlag(C4EnumeratorFlags.IncludeBodies); revs = enumerator.Select(x => new RevisionInternal(x.Document, includeBody)).ToList(); } } else { revs = new List<RevisionInternal> { new RevisionInternal(doc, 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; }
/// <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; }