//Create a response body for an HTTP response from a given list of DB changes, including all conflicts
        private static IDictionary<string, object> ResponseBodyForChanges(RevisionList changes, long since, int limit, DBMonitorCouchbaseResponseState state)
        {
            string lastDocId = null;
            IDictionary<string, object> lastEntry = null;
            var entries = new List<IDictionary<string, object>>();
            foreach (var rev in changes) {
                string docId = rev.DocID;
                if (docId.Equals(lastDocId)) {
                    ((IList)lastEntry["changes"]).Add(new Dictionary<string, object> { { "rev", rev.RevID } });
                } else {
                    lastEntry = ChangesDictForRev(rev, state);
                    entries.Add(lastEntry);
                    lastDocId = docId;
                }
            }
                    
            entries.Sort((x, y) => (int)((long)x["seq"] - (long)y["seq"]));
            if (entries.Count > limit) {
                entries.RemoveRange(limit, entries.Count - limit);
            }

            long lastSequence = entries.Any() ? (long)entries.Last()["seq"] : since;
            return new Dictionary<string, object> {
                { "results", entries },
                { "last_seq", lastSequence }
            };
        }
        /// <summary>
        /// Create a response body for an HTTP response from a given list of DB changes (no conflicts)
        /// </summary>
        /// <returns>The response body</returns>
        /// <param name="changes">The list of changes to be processed</param>
        /// <param name="since">The first change ID to be processed</param>
        /// <param name="responseState">The current response state</param>
        public static IDictionary<string, object> ResponseBodyForChanges(RevisionList changes, long since, DBMonitorCouchbaseResponseState responseState)
        {
            List<IDictionary<string, object>> results = new List<IDictionary<string, object>>();
            foreach (var change in changes) {
                results.Add(DatabaseMethods.ChangesDictForRev(change, responseState));
            }

            if (changes.Count > 0) {
                since = changes.Last().Sequence;
            }

            return new Dictionary<string, object> {
                { "results", results },
                { "last_seq", since }
            };
        }
 /// <summary>
 /// Creates a dictionary of metadata for one specific revision
 /// </summary>
 /// <returns>The metadata dictionary</returns>
 /// <param name="rev">The revision to examine</param>
 /// <param name="responseState">The current response state</param>
 public static IDictionary<string, object> ChangesDictForRev(RevisionInternal rev, DBMonitorCouchbaseResponseState responseState)
 {
     if (responseState.ChangesIncludeDocs) {
         var status = new Status();
         var rev2 = DocumentMethods.ApplyOptions(responseState.ContentOptions, rev, responseState.Context, responseState.Db, status);
         if (rev2 != null) {
             rev2.Sequence = rev.Sequence;
             rev = rev2;
         }
     }
     return new NonNullDictionary<string, object> {
         { "seq", rev.Sequence },
         { "id", rev.DocID },
         { "changes", new List<object> { 
                 new Dictionary<string, object> { 
                     { "rev", rev.RevID } 
                 } 
             } 
         },
         { "deleted", rev.Deleted ? (object)true : null },
         { "doc", responseState.ChangesIncludeDocs ? rev.GetProperties() : null }
     };
 }
        /// <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.GetLastSequenceNumber().ToString())) {
                        response.InternalStatus = StatusCode.NotModified;
                        return response;
                    }
                }
                    
                var options = ChangesOptions.Default;
                responseState.Db = db;
                responseState.ContentOptions = context.ContentOptions;
                responseState.ChangesFeedMode = context.ChangesFeedMode;
                responseState.ChangesIncludeDocs = context.GetQueryParam<bool>("include_docs", bool.TryParse, false);
                options.IncludeDocs = responseState.ChangesIncludeDocs;
                responseState.ChangesIncludeConflicts = context.GetQueryParam("style") == "all_docs";
                options.IncludeConflicts = responseState.ChangesIncludeConflicts;
                options.ContentOptions = context.ContentOptions;
                options.SortBySequence = !options.IncludeConflicts;
                options.Limit = context.GetQueryParam<int>("limit", int.TryParse, options.Limit);
                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) {
                            var success = response.SendContinuousLine(ChangesDictForRev(rev, responseState), context.ChangesFeedMode);
                            if(!success) {
                                return context.CreateResponse(StatusCode.BadRequest);
                            }
                        }
                    }

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

                        var heartbeatSpan = TimeSpan.FromMilliseconds(heartbeat);
                        if(heartbeatSpan < MinHeartbeat) {
                            heartbeatSpan = MinHeartbeat;
                        }

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

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

                    return response;
                }
            });

            responseState.Response = responseObject;
            return responseState;
        }
        public static ICouchbaseResponseState GetChangesPost(ICouchbaseListenerContext context)
        {
            DBMonitorCouchbaseResponseState responseState = new DBMonitorCouchbaseResponseState();

            var responseObject = PerformLogicWithDatabase(context, true, db =>
            {
                var response = context.CreateResponse();
                responseState.Response = response;
                var body = context.BodyAs<Dictionary<string, object>>();
                ProcessBody(body);
                if (body.GetCast<ChangesFeedMode>("feed") < ChangesFeedMode.Continuous) {
                    if(context.CacheWithEtag(db.GetLastSequenceNumber().ToString())) {
                        response.InternalStatus = StatusCode.NotModified;
                        return response;
                    }
                }

                var options = ChangesOptions.Default;
                responseState.Db = db;
                responseState.ContentOptions = body.GetCast<DocumentContentOptions>("content_options");
                responseState.ChangesFeedMode = body.GetCast<ChangesFeedMode>("feed");
                responseState.ChangesIncludeDocs = body.GetCast<bool>("include_docs");
                options.IncludeDocs = responseState.ChangesIncludeDocs;
                responseState.ChangesIncludeConflicts = body.GetCast<string>("style") == "all_docs";
                options.IncludeConflicts = responseState.ChangesIncludeConflicts;
                options.ContentOptions = responseState.ContentOptions;
                options.SortBySequence = !options.IncludeConflicts;
                options.Limit = body.GetCast<int>("limit", options.Limit);
                int since = body.GetCast<int>("since");

                string filterName = body.GetCast<string>("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((responseState.ChangesFeedMode >= ChangesFeedMode.Continuous) || 
                    (responseState.ChangesFeedMode == ChangesFeedMode.LongPoll && changes.Count == 0)) {
                    // Response is going to stay open (continuous, or hanging GET):
                    response.Chunked = true;
                    if(responseState.ChangesFeedMode == ChangesFeedMode.EventSource) {
                        response["Content-Type"] = "text/event-stream; charset=utf-8";
                    }

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

                    responseState.SubscribeToDatabase(db);
                    int heartbeat = body.GetCast<int>("heartbeat", Int32.MinValue);
                    if(heartbeat != Int32.MinValue) {
                        if(heartbeat <= 0) {
                            responseState.IsAsync = false;
                            return context.CreateResponse(StatusCode.BadParam);
                        }

                        heartbeat = Math.Max(heartbeat, (int)MinHeartbeat.TotalMilliseconds);
                        string heartbeatResponse = context.ChangesFeedMode == ChangesFeedMode.EventSource ? "\n\n" : "\r\n";
                        responseState.StartHeartbeat(heartbeatResponse, TimeSpan.FromMilliseconds(heartbeat));
                    }

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

                    return response;
                }
            });

            responseState.Response = responseObject;
            return responseState;
        }
Example #6
0
 /// <summary>
 /// Creates a dictionary of metadata for one specific revision
 /// </summary>
 /// <returns>The metadata dictionary</returns>
 /// <param name="rev">The revision to examine</param>
 /// <param name="responseState">The current response state</param>
 public static IDictionary <string, object> ChangesDictForRev(RevisionInternal rev, DBMonitorCouchbaseResponseState responseState)
 {
     if (responseState.ChangesIncludeDocs)
     {
         var status = new Status();
         var rev2   = DocumentMethods.ApplyOptions(responseState.ContentOptions, rev, responseState.Context, responseState.Db, status);
         if (rev2 != null)
         {
             rev2.SetSequence(rev.GetSequence());
             rev = rev2;
         }
     }
     return(new NonNullDictionary <string, object> {
         { "seq", rev.GetSequence() },
         { "id", rev.GetDocId() },
         { "changes", new List <object> {
               new Dictionary <string, object> {
                   { "rev", rev.GetRevId() }
               }
           } },
         { "deleted", rev.IsDeleted() ? (object)true : null },
         { "doc", responseState.ChangesIncludeDocs ? rev.GetProperties() : null }
     });
 }
Example #7
0
        //Create a response body for an HTTP response from a given list of DB changes, including all conflicts
        private static IDictionary <string, object> ResponseBodyForChanges(RevisionList changes, long since, int limit, DBMonitorCouchbaseResponseState state)
        {
            string lastDocId = null;
            IDictionary <string, object> lastEntry = null;
            var entries = new List <IDictionary <string, object> >();

            foreach (var rev in changes)
            {
                string docId = rev.GetDocId();
                if (docId.Equals(lastDocId))
                {
                    ((IList)lastEntry["changes"]).Add(new Dictionary <string, object> {
                        { "rev", rev.GetRevId() }
                    });
                }
                else
                {
                    lastEntry = ChangesDictForRev(rev, state);
                    entries.Add(lastEntry);
                    lastDocId = docId;
                }
            }

            entries.Sort((x, y) => (int)((long)x["seq"] - (long)y["seq"]));
            if (entries.Count > limit)
            {
                entries.RemoveRange(limit, entries.Count - limit);
            }

            long lastSequence = entries.Any() ? (long)entries.Last()["seq"] : since;

            return(new Dictionary <string, object> {
                { "results", entries },
                { "last_seq", lastSequence }
            });
        }
Example #8
0
        /// <summary>
        /// Create a response body for an HTTP response from a given list of DB changes (no conflicts)
        /// </summary>
        /// <returns>The response body</returns>
        /// <param name="changes">The list of changes to be processed</param>
        /// <param name="since">The first change ID to be processed</param>
        /// <param name="responseState">The current response state</param>
        public static IDictionary <string, object> ResponseBodyForChanges(RevisionList changes, long since, DBMonitorCouchbaseResponseState responseState)
        {
            List <IDictionary <string, object> > results = new List <IDictionary <string, object> >();

            foreach (var change in changes)
            {
                results.Add(DatabaseMethods.ChangesDictForRev(change, responseState));
            }

            if (changes.Count > 0)
            {
                since = changes.Last().GetSequence();
            }

            return(new Dictionary <string, object> {
                { "results", results },
                { "last_seq", since }
            });
        }
Example #9
0
        /// <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                           = ChangesOptions.Default;
                responseState.Db                      = db;
                responseState.ContentOptions          = context.ContentOptions;
                responseState.ChangesFeedMode         = context.ChangesFeedMode;
                responseState.ChangesIncludeDocs      = context.GetQueryParam <bool>("include_docs", bool.TryParse, false);
                options.IncludeDocs                   = responseState.ChangesIncludeDocs;
                responseState.ChangesIncludeConflicts = context.GetQueryParam("style") == "all_docs";
                options.IncludeConflicts              = responseState.ChangesIncludeConflicts;
                options.ContentOptions                = context.ContentOptions;
                options.SortBySequence                = !options.IncludeConflicts;
                options.Limit                         = context.GetQueryParam <int>("limit", int.TryParse, options.Limit);
                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.Limit, responseState));
                    }
                    else
                    {
                        response.JsonBody = new Body(ResponseBodyForChanges(changes, since, responseState));
                    }

                    return(response);
                }
            });

            responseState.Response = responseObject;
            return(responseState);
        }
Example #10
0
        public static ICouchbaseResponseState GetChangesPost(ICouchbaseListenerContext context)
        {
            DBMonitorCouchbaseResponseState responseState = new DBMonitorCouchbaseResponseState();

            var responseObject = PerformLogicWithDatabase(context, true, db =>
            {
                var response           = context.CreateResponse();
                responseState.Response = response;
                var body = context.BodyAs <Dictionary <string, object> >();
                ProcessBody(body);
                if (body.GetCast <ChangesFeedMode>("feed") < ChangesFeedMode.Continuous)
                {
                    if (context.CacheWithEtag(db.GetLastSequenceNumber().ToString()))
                    {
                        response.InternalStatus = StatusCode.NotModified;
                        return(response);
                    }
                }

                var options                           = ChangesOptions.Default;
                responseState.Db                      = db;
                responseState.ContentOptions          = body.GetCast <DocumentContentOptions>("content_options");
                responseState.ChangesFeedMode         = body.GetCast <ChangesFeedMode>("feed");
                responseState.ChangesIncludeDocs      = body.GetCast <bool>("include_docs");
                options.IncludeDocs                   = responseState.ChangesIncludeDocs;
                responseState.ChangesIncludeConflicts = body.GetCast <string>("style") == "all_docs";
                options.IncludeConflicts              = responseState.ChangesIncludeConflicts;
                options.ContentOptions                = responseState.ContentOptions;
                options.SortBySequence                = !options.IncludeConflicts;
                options.Limit                         = body.GetCast <int>("limit", options.Limit);
                int since = body.GetCast <int>("since");

                string filterName = body.GetCast <string>("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();
                }


                if (responseState.ChangesFeedMode >= ChangesFeedMode.LongPoll)
                {
                    // Response is going to stay open (continuous, or hanging GET):
                    response.Chunked = true;
                    if (responseState.ChangesFeedMode == ChangesFeedMode.EventSource)
                    {
                        response["Content-Type"] = "text/event-stream; charset=utf-8";
                    }

                    response.WriteHeaders();
                    if (responseState.SubscribeToDatabase(db, since, options))
                    {
                        int heartbeat = body.GetCast <int>("heartbeat", Int32.MinValue);
                        if (heartbeat != Int32.MinValue)
                        {
                            if (heartbeat <= 0)
                            {
                                responseState.IsAsync = false;
                                return(context.CreateResponse(StatusCode.BadParam));
                            }

                            heartbeat = Math.Max(heartbeat, (int)MinHeartbeat.TotalMilliseconds);
                            string heartbeatResponse = context.ChangesFeedMode == ChangesFeedMode.EventSource ? "\n\n" : "\r\n";
                            responseState.StartHeartbeat(heartbeatResponse, TimeSpan.FromMilliseconds(heartbeat));
                        }
                    }

                    return(responseState.Response);
                }
                else
                {
                    var changes      = db.ChangesSinceStreaming(since, options, responseState.ChangesFilter, responseState.FilterParams);
                    response.Chunked = true;
                    response.Headers["Content-Type"] = "application/json";
                    response.WriteBodyCallback       = WriteChangesBodyJson;
                    if (responseState.ChangesIncludeConflicts)
                    {
                        response.WriteBodyContext = new WriteChangesContext {
                            IncludeConflicts = true,
                            Since            = since,
                            Limit            = options.Limit,
                            Changes          = changes,
                            ResponseState    = responseState
                        };
                    }
                    else
                    {
                        response.WriteBodyContext = new WriteChangesContext {
                            Since         = since,
                            Changes       = changes,
                            ResponseState = responseState
                        };
                    }

                    return(response);
                }
            });

            responseState.Response = responseObject;
            return(responseState);
        }
Example #11
0
        /// <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.GetLastSequenceNumber().ToString()))
                    {
                        response.InternalStatus = StatusCode.NotModified;
                        return(response);
                    }
                }

                var options                           = ChangesOptions.Default;
                responseState.Db                      = db;
                responseState.ContentOptions          = context.ContentOptions;
                responseState.ChangesFeedMode         = context.ChangesFeedMode;
                responseState.ChangesIncludeDocs      = context.GetQueryParam <bool>("include_docs", bool.TryParse, false);
                options.IncludeDocs                   = responseState.ChangesIncludeDocs;
                responseState.ChangesIncludeConflicts = context.GetQueryParam("style") == "all_docs";
                options.IncludeConflicts              = responseState.ChangesIncludeConflicts;
                options.ContentOptions                = context.ContentOptions;
                options.SortBySequence                = !options.IncludeConflicts;
                options.Limit                         = context.GetQueryParam <int>("limit", int.TryParse, options.Limit);
                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)
                    {
                        var code = Database.FilterCompiler == null ? StatusCode.NotImplemented : StatusCode.NotFound;
                        return(context.CreateResponse(code));
                    }

                    responseState.FilterParams = context.GetQueryParams();
                }

                string heartbeatParam = context.GetQueryParam("heartbeat");
                int heartbeat         = 0;
                if (heartbeatParam != null && (!Int32.TryParse(heartbeatParam, out heartbeat) || heartbeat <= 0))
                {
                    return(context.CreateResponse(StatusCode.BadParam));
                }

                var changes = db.ChangesSinceStreaming(since, options, responseState.ChangesFilter, responseState.FilterParams);
                if ((context.ChangesFeedMode >= ChangesFeedMode.Continuous) ||
                    (context.ChangesFeedMode == ChangesFeedMode.LongPoll && !changes.Any()))
                {
                    // 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)
                        {
                            var success = response.SendContinuousLine(ChangesDictForRev(rev, responseState), context.ChangesFeedMode);
                            if (!success)
                            {
                                return(context.CreateResponse(StatusCode.BadRequest));
                            }
                        }
                    }

                    var subscribed = responseState.SubscribeToDatabase(db, since, options);
                    if (!subscribed)
                    {
                        return(response);
                    }

                    if (heartbeat > 0)
                    {
                        var heartbeatSpan = TimeSpan.FromMilliseconds(heartbeat);
                        if (heartbeatSpan < MinHeartbeat)
                        {
                            heartbeatSpan = MinHeartbeat;
                        }

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

                    return(response);
                }
                else
                {
                    response.Chunked = true;
                    response.Headers["Content-Type"] = "application/json";
                    response.WriteBodyCallback       = WriteChangesBodyJson;
                    if (responseState.ChangesIncludeConflicts)
                    {
                        response.WriteBodyContext = new WriteChangesContext {
                            IncludeConflicts = true,
                            Since            = since,
                            Limit            = options.Limit,
                            Changes          = changes,
                            ResponseState    = responseState
                        };
                    }
                    else
                    {
                        response.WriteBodyContext = new WriteChangesContext {
                            Since         = since,
                            Changes       = changes,
                            ResponseState = responseState
                        };
                    }
                }

                return(response);
            });

            responseState.Response = responseObject;
            return(responseState);
        }
 /// <summary>
 /// Creates a dictionary of metadata for one specific revision
 /// </summary>
 /// <returns>The metadata dictionary</returns>
 /// <param name="rev">The revision to examine</param>
 /// <param name="responseState">The current response state</param>
 public static IDictionary <string, object> ChangesDictForRev(RevisionInternal rev, DBMonitorCouchbaseResponseState responseState)
 {
     return(new NonNullDictionary <string, object> {
         { "seq", rev.GetSequence() },
         { "id", rev.GetDocId() },
         { "changes", new List <object> {
               new Dictionary <string, object> {
                   { "rev", rev.GetRevId() }
               }
           } },
         { "deleted", rev.IsDeleted() ? (object)true : null },
         { "doc", responseState.ChangesIncludeDocs ? rev.GetProperties() : null }
     });
 }