/// <summary>
        /// Uploads the supplied content as an attachment to the specified document.
        /// </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/document/attachments.html#put--db-docid-attname
        /// </remarks>
        public static ICouchbaseResponseState UpdateAttachment(ICouchbaseListenerContext context)
        {
            var state = new AsyncOpCouchbaseResponseState();

            DatabaseMethods.PerformLogicWithDatabase(context, true, db =>
            {
                var blob     = db.AttachmentWriter;
                var httpBody = new byte[context.ContentLength];
                context.BodyStream.ReadAsync(httpBody, 0, httpBody.Length).ContinueWith(t => {
                    if (t.Result == 0)
                    {
                        state.Response = context.CreateResponse(StatusCode.BadAttachment);
                        state.SignalFinished();
                        return;
                    }

                    blob.AppendData(httpBody);
                    blob.Finish();

                    state.Response = UpdateAttachment(context, db, context.AttachmentName, context.DocumentName, blob);
                    state.SignalFinished();
                });

                return(null);
            });

            return(state);
        }
        /// <summary>
        /// Returns the file attachment associated with the document. The raw data of the associated attachment is returned
        /// (just as if you were accessing a static file. The returned Content-Type will be the same as the content type
        /// set when the document attachment was submitted into the database.
        /// </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/document/attachments.html#get--db-docid-attname
        /// </remarks>
        public static ICouchbaseResponseState GetAttachment(ICouchbaseListenerContext context)
        {
            return(DatabaseMethods.PerformLogicWithDatabase(context, true, db =>
            {
                Status status = new Status();
                var revID = context.GetQueryParam("rev");
                var rev = db.GetDocument(context.DocumentName, revID == null ? null : revID.AsRevID(), false, status);

                if (rev == null)
                {
                    return context.CreateResponse(status.Code);
                }
                if (context.CacheWithEtag(rev.RevID.ToString()))
                {
                    return context.CreateResponse(StatusCode.NotModified);
                }

                string acceptEncoding = context.RequestHeaders["Accept-Encoding"];
                bool acceptEncoded = acceptEncoding != null && acceptEncoding.Contains("gzip") &&
                                     context.RequestHeaders["Range"] == null;

                var attachment = db.GetAttachmentForRevision(rev, context.AttachmentName);
                if (attachment == null)
                {
                    return context.CreateResponse(StatusCode.AttachmentNotFound);
                }

                var response = context.CreateResponse();
                if (context.Method.Equals(HttpMethod.Head))
                {
                    var length = attachment.Length;
                    if (acceptEncoded && attachment.Encoding == AttachmentEncoding.GZIP &&
                        attachment.EncodedLength > 0)
                    {
                        length = attachment.EncodedLength;
                    }

                    response["Content-Length"] = length.ToString();
                }
                else
                {
                    var contents = acceptEncoded ? attachment.EncodedContent : attachment.Content;
                    if (contents == null)
                    {
                        response.InternalStatus = StatusCode.NotFound;
                        return response;
                    }

                    response.BinaryBody = contents;
                }

                response["Content-Type"] = attachment.ContentType;
                if (acceptEncoded && attachment.Encoding == AttachmentEncoding.GZIP)
                {
                    response["Content-Encoding"] = "gzip";
                }

                return response;
            }).AsDefaultState());
        }
 /// <summary>
 /// Marks the specified document as deleted by adding a field _deleted with the value true.
 /// Documents with this field will not be returned within requests anymore, but stay in the database.
 /// </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/document/common.html#delete--db-docid
 /// </remarks>
 public static ICouchbaseResponseState DeleteDocument(ICouchbaseListenerContext context)
 {
     return(DatabaseMethods.PerformLogicWithDatabase(context, true, db =>
     {
         string docId = context.DocumentName;
         return UpdateDb(context, db, docId, null, true);
     }).AsDefaultState());
 }
Exemplo n.º 4
0
        // Factors out the logic of opening the database and reading the document body from the HTTP request
        // and performs the specified logic on the body received in the request, barring any problems
        private static CouchbaseLiteResponse PerformLogicWithDocumentBody(ICouchbaseListenerContext context,
                                                                          Func <Database, Body, CouchbaseLiteResponse> callback)
        {
            return(DatabaseMethods.PerformLogicWithDatabase(context, true, db =>
            {
                MultipartDocumentReader reader = new MultipartDocumentReader(db);
                reader.SetContentType(context.RequestHeaders["Content-Type"]);
                reader.AppendData(context.BodyStream.ReadAllBytes());
                try {
                    reader.Finish();
                } catch (InvalidOperationException) {
                    return context.CreateResponse(StatusCode.BadRequest);
                }

                return callback(db, new Body(reader.GetDocumentProperties()));
            }));
        }
Exemplo n.º 5
0
        // Performs the actual query logic on a design document
        private static CouchbaseLiteResponse QueryDesignDocument(ICouchbaseListenerContext context, IList <object> keys)
        {
            return(DatabaseMethods.PerformLogicWithDatabase(context, true, db =>
            {
                var view = db.GetView(String.Format("{0}/{1}", context.DesignDocName, context.ViewName));
                var status = view.CompileFromDesignDoc();
                if (status.IsError)
                {
                    return context.CreateResponse(status.Code);
                }

                var options = context.QueryOptions;
                if (options == null)
                {
                    return context.CreateResponse(StatusCode.BadRequest);
                }

                if (keys != null)
                {
                    options.Keys = keys;
                }

                if (options.Stale == IndexUpdateMode.Before || view.LastSequenceIndexed <= 0)
                {
                    view.UpdateIndex();
                }
                else if (options.Stale == IndexUpdateMode.After && view.LastSequenceIndexed < db.LastSequenceNumber)
                {
                    db.RunAsync(_ => view.UpdateIndex());
                }

                // Check for conditional GET and set response Etag header:
                if (keys == null)
                {
                    long eTag = options.IncludeDocs ? db.LastSequenceNumber : view.LastSequenceIndexed;
                    if (context.CacheWithEtag(eTag.ToString()))
                    {
                        return context.CreateResponse(StatusCode.NotModified);
                    }
                }

                return DatabaseMethods.QueryView(context, db, view, options);
            }));
        }
        // Factors out the logic of opening the database and reading the document body from the HTTP request
        // and performs the specified logic on the body received in the request, barring any problems
        private static CouchbaseLiteResponse PerformLogicWithDocumentBody(ICouchbaseListenerContext context,
                                                                          Func <Database, Body, CouchbaseLiteResponse> callback)
        {
            return(DatabaseMethods.PerformLogicWithDatabase(context, true, db =>
            {
                MultipartDocumentReader reader = new MultipartDocumentReader(db);
                reader.SetContentType(context.RequestHeaders["Content-Type"]);

                try {
                    reader.AppendData(context.BodyStream.ReadAllBytes());
                    reader.Finish();
                } catch (InvalidOperationException e) {
                    Log.To.Router.E(TAG, "Exception trying to read data from multipart upload", e);
                    return context.CreateResponse(StatusCode.BadRequest);
                } catch (IOException e) {
                    Log.To.Router.E(TAG, "IOException while reading context body", e);
                    return context.CreateResponse(StatusCode.RequestTimeout);
                }

                return callback(db, new Body(reader.GetDocumentProperties()));
            }));
        }
        /// <summary>
        /// Returns document by the specified docid from the specified db. Unless you request a
        /// specific revision, the latest revision of the document will always be returned.
        /// </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/document/common.html#get--db-docid
        /// </remarks>
        public static ICouchbaseResponseState GetDocument(ICouchbaseListenerContext context)
        {
            return(DatabaseMethods.PerformLogicWithDatabase(context, true, db => {
                var response = context.CreateResponse();
                string docId = context.DocumentName;
                bool isLocalDoc = docId.StartsWith("_local", StringComparison.InvariantCulture);

                DocumentContentOptions options = context.ContentOptions;
                string openRevsParam = context.GetQueryParam("open_revs");
                bool mustSendJson = context.ExplicitlyAcceptsType("application/json");
                Status status = new Status();
                if (openRevsParam == null || isLocalDoc)
                {
                    //Regular GET:
                    var revId = context.GetQueryParam("rev").AsRevID(); //often null
                    RevisionInternal rev;
                    bool includeAttachments = false, sendMultipart = false;
                    if (isLocalDoc)
                    {
                        rev = db.Storage.GetLocalDocument(docId, revId);
                    }
                    else
                    {
                        includeAttachments = options.HasFlag(DocumentContentOptions.IncludeAttachments);
                        if (includeAttachments)
                        {
                            sendMultipart = !mustSendJson;
                            options &= ~DocumentContentOptions.IncludeAttachments;
                        }


                        rev = db.GetDocument(docId, revId, true, status);
                        if (rev != null)
                        {
                            rev = ApplyOptions(options, rev, context, db, status);
                        }

                        if (rev == null)
                        {
                            response.StatusReason = status.Code == StatusCode.Deleted ? "deleted" : "missing";
                            response.InternalStatus = StatusCode.NotFound;
                            return response;
                        }
                    }

                    if (rev == null)
                    {
                        response.InternalStatus = StatusCode.NotFound;
                        return response;
                    }

                    if (context.CacheWithEtag(rev.RevID?.ToString()))
                    {
                        response.InternalStatus = StatusCode.NotModified;
                        return response;
                    }

                    if (!isLocalDoc && includeAttachments)
                    {
                        int minRevPos = 1;
                        var attsSince = context.GetJsonQueryParam("atts_since")?.AsList <string>()?.AsRevIDs();
                        var ancestorId = db.Storage.FindCommonAncestor(rev, attsSince);
                        if (ancestorId != null)
                        {
                            minRevPos = ancestorId.Generation + 1;
                        }

                        bool attEncodingInfo = context.GetQueryParam <bool>("att_encoding_info", bool.TryParse, false);
                        db.ExpandAttachments(rev, minRevPos, sendMultipart, attEncodingInfo);
                    }

                    if (sendMultipart)
                    {
                        response.MultipartWriter = MultipartWriterForRev(db, rev, "multipart/related");
                    }
                    else
                    {
                        response.JsonBody = rev.GetBody();
                    }
                }
                else
                {
                    // open_revs query:
                    IList <IDictionary <string, object> > result;
                    if (openRevsParam.Equals("all"))
                    {
                        // ?open_revs=all returns all current/leaf revisions:
                        bool includeDeleted = context.GetQueryParam <bool>("include_deleted", bool.TryParse, false);
                        RevisionList allRevs = db.Storage.GetAllDocumentRevisions(docId, true, includeDeleted);

                        result = new List <IDictionary <string, object> >();
                        foreach (var rev in allRevs)
                        {
                            if (!includeDeleted && rev.Deleted)
                            {
                                continue;
                            }

                            var loadedRev = db.RevisionByLoadingBody(rev, status);
                            if (loadedRev != null)
                            {
                                ApplyOptions(options, loadedRev, context, db, status);
                            }

                            if (loadedRev != null)
                            {
                                result.Add(new Dictionary <string, object> {
                                    { "ok", loadedRev.GetProperties() }
                                });
                            }
                            else if (status.Code <= StatusCode.InternalServerError)
                            {
                                result.Add(new Dictionary <string, object> {
                                    { "missing", rev.RevID }
                                });
                            }
                            else
                            {
                                response.InternalStatus = status.Code;
                                return response;
                            }
                        }
                    }
                    else
                    {
                        // ?open_revs=[...] returns an array of specific revisions of the document:
                        var openRevs = context.GetJsonQueryParam("open_revs").AsList <object>();
                        if (openRevs == null)
                        {
                            response.InternalStatus = StatusCode.BadParam;
                            return response;
                        }

                        result = new List <IDictionary <string, object> >();
                        foreach (var revIDObj in openRevs)
                        {
                            var revID = revIDObj as string;
                            if (revID == null)
                            {
                                response.InternalStatus = StatusCode.BadId;
                                return response;
                            }

                            var rev = db.GetDocument(docId, revID.AsRevID(), true);
                            if (rev != null)
                            {
                                rev = ApplyOptions(options, rev, context, db, status);
                            }

                            if (rev != null)
                            {
                                result.Add(new Dictionary <string, object> {
                                    { "ok", rev.GetProperties() }
                                });
                            }
                            else
                            {
                                result.Add(new Dictionary <string, object> {
                                    { "missing", revID }
                                });
                            }
                        }
                    }

                    if (mustSendJson)
                    {
                        response["Content-Type"] = "application/json";
                        response.JsonBody = new Body(result.Cast <object>().ToList());
                    }
                    else
                    {
                        response.SetMultipartBody(result.Cast <object>().ToList(), "multipart/mixed");
                    }
                }

                return response;
            }).AsDefaultState());
        }
 /// <summary>
 /// Deletes the attachment of the specified doc.
 /// </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/document/attachments.html#delete--db-docid-attname
 /// </remarks>
 public static ICouchbaseResponseState DeleteAttachment(ICouchbaseListenerContext context)
 {
     return(DatabaseMethods.PerformLogicWithDatabase(context, true, db =>
                                                     UpdateAttachment(context, db, context.AttachmentName, context.DocumentName, null)).AsDefaultState());
 }