private MultipartWriter GetMultipartWriter(RevisionInternal rev, string boundary)
        {
            // Find all the attachments with "follows" instead of a body, and put 'em in a multipart stream.
            // It's important to scan the _attachments entries in the same order in which they will appear
            // in the JSON, because CouchDB expects the MIME bodies to appear in that same order
            var bodyStream  = default(MultipartWriter);
            var attachments = rev.GetAttachments();

            foreach (var a in attachments)
            {
                var attachment = a.Value.AsDictionary <string, object>();
                if (attachment != null && attachment.GetCast <bool>("follows"))
                {
                    if (bodyStream == null)
                    {
                        // Create the HTTP multipart stream:
                        bodyStream = new MultipartWriter("multipart/related", boundary);
                        bodyStream.SetNextPartHeaders(new Dictionary <string, string> {
                            { "Content-Type", "application/json" }
                        });

                        // Use canonical JSON encoder so that _attachments keys will be written in the
                        // same order that this for loop is processing the attachments.
                        var json = Manager.GetObjectMapper().WriteValueAsBytes(rev.GetProperties(), true);
                        if (CanSendCompressedRequests)
                        {
                            bodyStream.AddGZippedData(json);
                        }
                        else
                        {
                            bodyStream.AddData(json);
                        }
                    }

                    // Add attachment as another MIME part:
                    var disposition     = String.Format("attachment; filename={0}", Misc.QuoteString(a.Key));
                    var contentType     = attachment.GetCast <string>("type");
                    var contentEncoding = attachment.GetCast <string>("encoding");
                    bodyStream.SetNextPartHeaders(new NonNullDictionary <string, string> {
                        { "Content-Disposition", disposition },
                        { "Content-Type", contentType },
                        { "Content-Encoding", contentEncoding }
                    });

                    var attachmentObj = default(AttachmentInternal);
                    try {
                        attachmentObj = LocalDatabase.AttachmentForDict(attachment, a.Key);
                    } catch (CouchbaseLiteException) {
                        return(null);
                    }

                    bodyStream.AddStream(attachmentObj.ContentStream, attachmentObj.Length);
                }
            }

            return(bodyStream);
        }
        private static MultipartWriter MultipartWriterForRev(Database db, RevisionInternal rev, string contentType)
        {
            var writer = new MultipartWriter(contentType, null);

            writer.SetNextPartHeaders(new Dictionary <string, string> {
                { "Content-Type", "application/json" }
            });
            writer.AddData(rev.GetBody().AsJson());
            var attachments = rev.GetAttachments();

            if (attachments == null)
            {
                return(writer);
            }

            foreach (var entry in attachments)
            {
                var attachment = entry.Value.AsDictionary <string, object>();
                if (attachment != null && attachment.GetCast <bool>("follows", false))
                {
                    var disposition = String.Format("attachment; filename={0}", Database.Quote(entry.Key));
                    writer.SetNextPartHeaders(new Dictionary <string, string> {
                        { "Content-Disposition", disposition }
                    });

                    var attachObj = default(AttachmentInternal);
                    try {
                        attachObj = db.AttachmentForDict(attachment, entry.Key);
                    } catch (CouchbaseLiteException) {
                        return(null);
                    }

                    var fileURL = attachObj.ContentUrl;
                    if (fileURL != null)
                    {
                        writer.AddFileUrl(fileURL);
                    }
                    else
                    {
                        writer.AddStream(attachObj.ContentStream);
                    }
                }
            }

            return(writer);
        }
Beispiel #3
0
        public void ForceInsert(RevisionInternal inRev, IList <string> revHistory, StoreValidation validationBlock, Uri source)
        {
            if (_config.HasFlag(C4DatabaseFlags.ReadOnly))
            {
                throw new CouchbaseLiteException("Attempting to write to a readonly database", StatusCode.Forbidden);
            }

            var json   = Manager.GetObjectMapper().WriteValueAsString(inRev.GetProperties(), true);
            var change = default(DocumentChange);

            RunInTransaction(() =>
            {
                // First get the CBForest doc:
                WithC4Document(inRev.GetDocId(), null, false, true, doc =>
                {
                    ForestDBBridge.Check(err => Native.c4doc_insertRevisionWithHistory(doc, json, inRev.IsDeleted(),
                                                                                       inRev.GetAttachments() != null, revHistory.ToArray(), err));

                    // Save updated doc back to the database:
                    var isWinner = SaveDocument(doc, revHistory[0], inRev.GetProperties());
                    inRev.SetSequence((long)doc->sequence);
                    change = ChangeWithNewRevision(inRev, isWinner, doc, source);
                });

                return(true);
            });

            if (change != null && Delegate != null)
            {
                Delegate.DatabaseStorageChanged(change);
            }
        }
Beispiel #4
0
        public RevisionInternal PutRevision(string inDocId, string inPrevRevId, IDictionary <string, object> properties,
                                            bool deleting, bool allowConflict, StoreValidation validationBlock)
        {
            if (_config.HasFlag(C4DatabaseFlags.ReadOnly))
            {
                throw new CouchbaseLiteException("Attempting to write to a readonly database", StatusCode.Forbidden);
            }

            var json = default(string);

            if (properties != null)
            {
                json = Manager.GetObjectMapper().WriteValueAsString(Database.StripDocumentJSON(properties), true);
            }
            else
            {
                json = "{}";
            }

            if (inDocId == null)
            {
                inDocId = Misc.CreateGUID();
            }

            var putRev  = default(RevisionInternal);
            var change  = default(DocumentChange);
            var success = RunInTransaction(() =>
            {
                var docId              = inDocId;
                var prevRevId          = inPrevRevId;
                var transactionSuccess = false;
                WithC4Document(docId, null, false, true, doc =>
                {
                    if (prevRevId != null)
                    {
                        // Updating an existing revision; make sure it exists and is a leaf:
                        ForestDBBridge.Check(err => Native.c4doc_selectRevision(doc, prevRevId, false, err));
                        if (!allowConflict && !doc->selectedRev.IsLeaf)
                        {
                            throw new CouchbaseLiteException(StatusCode.Conflict);
                        }
                    }
                    else
                    {
                        // No parent revision given:
                        if (deleting)
                        {
                            // Didn't specify a revision to delete: NotFound or a Conflict, depending
                            throw new CouchbaseLiteException(doc->Exists ? StatusCode.Conflict : StatusCode.NotFound);
                        }

                        // If doc exists, current rev must be in a deleted state or there will be a conflict:
                        if (Native.c4doc_selectCurrentRevision(doc))
                        {
                            if (doc->selectedRev.IsDeleted)
                            {
                                // New rev will be child of the tombstone:
                                prevRevId = (string)doc->revID;
                            }
                            else
                            {
                                throw new CouchbaseLiteException(StatusCode.Conflict);
                            }
                        }
                    }

                    // Compute the new revID. (Can't be done earlier because prevRevID may have changed.)
                    var newRevID = Delegate != null ? Delegate.GenerateRevID(Encoding.UTF8.GetBytes(json), deleting, prevRevId) : null;
                    if (newRevID == null)
                    {
                        throw new CouchbaseLiteException(StatusCode.BadId);
                    }

                    putRev = new RevisionInternal(docId, newRevID, deleting);
                    if (properties != null)
                    {
                        properties["_id"]  = docId;
                        properties["_rev"] = newRevID;
                        putRev.SetProperties(properties);
                    }

                    // Run any validation blocks:
                    if (validationBlock != null)
                    {
                        var prevRev = default(RevisionInternal);
                        if (prevRevId != null)
                        {
                            prevRev = new RevisionInternal(docId, prevRevId, doc->selectedRev.IsDeleted);
                        }

                        var status = validationBlock(putRev, prevRev, prevRevId);
                        if (status.IsError)
                        {
                            throw new CouchbaseLiteException(String.Format("{0} failed validation", putRev),
                                                             status.Code);
                        }
                    }

                    // Add the revision to the database:
                    ForestDBBridge.Check(err => Native.c4doc_insertRevision(doc, newRevID, json, deleting,
                                                                            putRev.GetAttachments() != null, allowConflict, err));
                    var isWinner = SaveDocument(doc, newRevID, properties);
                    putRev.SetSequence((long)doc->sequence);
                    change             = ChangeWithNewRevision(putRev, isWinner, doc, null);
                    transactionSuccess = true;
                });

                return(transactionSuccess);
            });

            if (!success)
            {
                return(null);
            }

            if (Delegate != null && change != null)
            {
                Delegate.DatabaseStorageChanged(change);
            }

            return(putRev);
        }