Пример #1
0
        /// <summary>Updates or deletes an attachment, creating a new document revision in the process.
        ///     </summary>
        /// <remarks>
        /// Updates or deletes an attachment, creating a new document revision in the process.
        /// Used by the PUT / DELETE methods called on attachment URLs.
        /// </remarks>
        /// <exclude></exclude>
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
        internal RevisionInternal UpdateAttachment(string filename, BlobStoreWriter body, string contentType, AttachmentEncoding encoding, string docID, string oldRevID)
        {
            if(StringEx.IsNullOrWhiteSpace(filename) || (body != null && contentType == null) || 
                (oldRevID != null && docID == null) || (body != null && docID == null)) {
                throw new CouchbaseLiteException(StatusCode.BadAttachment);
            }

            var oldRev = new RevisionInternal(docID, oldRevID, false);
            if (oldRevID != null) {
                // Load existing revision if this is a replacement:
                try {
                    oldRev = LoadRevisionBody(oldRev);
                } catch (CouchbaseLiteException e) {
                    if (e.Code == StatusCode.NotFound && GetDocument(docID, null, false) != null) {
                        throw new CouchbaseLiteException(StatusCode.Conflict);
                    }

                    throw;
                }
            } else {
                // If this creates a new doc, it needs a body:
                oldRev.SetBody(new Body(new Dictionary<string, object>()));
            }

            // Update the _attachments dictionary:
            var attachments = oldRev.GetProperties().Get("_attachments").AsDictionary<string, object>();
            if (attachments == null) {
                attachments = new Dictionary<string, object>();
            }

            if (body != null) {
                var key = body.GetBlobKey();
                string digest = key.Base64Digest();
                RememberAttachmentWriter(body);
                string encodingName = (encoding == AttachmentEncoding.GZIP) ? "gzip" : null;
                attachments[filename] = new NonNullDictionary<string, object> {
                    { "digest", digest },
                    { "length", body.GetLength() },
                    { "follows", true },
                    { "content_type", contentType },
                    { "encoding", encodingName }
                };
            } else {
                if (oldRevID != null && attachments.Get(filename) == null) {
                    throw new CouchbaseLiteException(StatusCode.AttachmentNotFound);
                }

                attachments.Remove(filename);
            }

            var properties = oldRev.GetProperties();
            properties["_attachments"] = attachments;
            oldRev.SetProperties(properties);

            Status status = new Status();
            var newRev = PutRevision(oldRev, oldRevID, false, status);
            if (status.IsError) {
                throw new CouchbaseLiteException(status.Code);
            }

            return newRev;
        }
        public RevisionInternal GetParentRevision(RevisionInternal rev)
        {
            var retVal = default(RevisionInternal);
            WithC4Document(rev.GetDocId(), rev.GetRevId(), false, false, doc =>
            {
                if (!Native.c4doc_selectParentRevision(doc)) {
                    return;
                }
                    
                ForestDBBridge.Check(err => Native.c4doc_loadRevisionBody(doc, err));
                retVal = new RevisionInternal((string)doc->docID, (string)doc->selectedRev.revID, doc->selectedRev.IsDeleted);
                retVal.SetSequence((long)doc->selectedRev.sequence);
                retVal.SetBody(new Body(doc->selectedRev.body));
            });

            return retVal;
        }
Пример #3
0
        /// <summary>Updates or deletes an attachment, creating a new document revision in the process.
        ///     </summary>
        /// <remarks>
        /// Updates or deletes an attachment, creating a new document revision in the process.
        /// Used by the PUT / DELETE methods called on attachment URLs.
        /// </remarks>
        /// <exclude></exclude>
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
        internal RevisionInternal UpdateAttachment(string filename, BlobStoreWriter body, string contentType, AttachmentEncoding encoding, string docID, string oldRevID)
        {
            var isSuccessful = false;
            if (String.IsNullOrEmpty (filename) || (body != null && contentType == null) || (oldRevID != null && docID == null) || (body != null && docID == null))
            {
                throw new CouchbaseLiteException(StatusCode.BadRequest);
            }

            BeginTransaction();

            try
            {
                var oldRev = new RevisionInternal(docID, oldRevID, false, this);
                if (oldRevID != null)
                {
                    // Load existing revision if this is a replacement:
                    try
                    {
                        LoadRevisionBody(oldRev, DocumentContentOptions.None);
                    }
                    catch (CouchbaseLiteException e)
                    {
                        if (e.GetCBLStatus().GetCode() == StatusCode.NotFound && ExistsDocumentWithIDAndRev(docID, null))
                        {
                            throw new CouchbaseLiteException(StatusCode.Conflict);
                        }
                    }
                }
                else
                {
                    // If this creates a new doc, it needs a body:
                    oldRev.SetBody(new Body(new Dictionary<string, object>()));
                }
                // Update the _attachments dictionary:
                var oldRevProps = oldRev.GetProperties();
                IDictionary<string, object> attachments = null;
                if (oldRevProps != null)
                {
                    attachments = (IDictionary<string, object>)oldRevProps.Get("_attachments");
                }
                if (attachments == null)
                {
                    attachments = new Dictionary<string, object>();
                }
                if (body != null)
                {
                    var key = body.GetBlobKey();
                    var digest = key.Base64Digest();

                    var blobsByDigest = new Dictionary<string, BlobStoreWriter>();
                    blobsByDigest.Put(digest, body);

                    RememberAttachmentWritersForDigests(blobsByDigest);

                    var encodingName = (encoding == AttachmentEncoding.AttachmentEncodingGZIP) ? "gzip" : null;
                    var dict = new Dictionary<string, object>();
                    dict.Put("digest", digest);
                    dict.Put("length", body.GetLength());
                    dict.Put("follows", true);
                    dict.Put("content_type", contentType);
                    dict.Put("encoding", encodingName);

                    attachments.Put(filename, dict);
                }
                else
                {
                    if (oldRevID != null && !attachments.ContainsKey(filename))
                    {
                        throw new CouchbaseLiteException(StatusCode.NotFound);
                    }
                    attachments.Remove(filename);
                }

                var properties = oldRev.GetProperties();
                properties.Put("_attachments", attachments);
                oldRev.SetProperties(properties);

                // Create a new revision:
                var putStatus = new Status();
                var newRev = PutRevision(oldRev, oldRevID, false, putStatus);

                isSuccessful = true;

                return newRev;
            }
            catch (SQLException e)
            {
                Log.E(Tag, "Error updating attachment", e);
                throw new CouchbaseLiteException(StatusCode.InternalServerError);
            }
            finally
            {
                EndTransaction(isSuccessful);
            }
        }
        /// <summary>
        /// Attempt to update a document based on the information in the HTTP request
        /// </summary>
        /// <returns>The resulting status of the operation</returns>
        /// <param name="context">The request context</param>
        /// <param name="db">The database in which the document exists</param>
        /// <param name="docId">The ID of the document being updated</param>
        /// <param name="body">The new document body</param>
        /// <param name="deleting">Whether or not the document is being deleted</param>
        /// <param name="allowConflict">Whether or not to allow a conflict to be inserted</param>
        /// <param name="outRev">The resulting revision of the document</param>
        public static StatusCode UpdateDocument(ICouchbaseListenerContext context, Database db, string docId, Body body, bool deleting, 
            bool allowConflict, out RevisionInternal outRev)
        {
            outRev = null;
            if (body != null && !body.IsValidJSON()) {
                return StatusCode.BadJson;
            }

            string prevRevId;
            if (!deleting) {
                var properties = body.GetProperties();
                deleting = properties.GetCast<bool>("_deleted");
                if (docId == null) {
                    // POST's doc ID may come from the _id field of the JSON body.
                    docId = properties.GetCast<string>("_id");
                    if (docId == null && deleting) {
                        return StatusCode.BadId;
                    }
                }

                // PUT's revision ID comes from the JSON body.
                prevRevId = properties.GetCast<string>("_rev");
            } else {
                // DELETE's revision ID comes from the ?rev= query param
                prevRevId = context.GetQueryParam("rev");
            }

            // A backup source of revision ID is an If-Match header:
            if (prevRevId == null) {
                prevRevId = context.IfMatch();
            }

            if (docId == null && deleting) {
                return StatusCode.BadId;
            }

            RevisionInternal rev = new RevisionInternal(docId, null, deleting);
            rev.SetBody(body);

            StatusCode status = StatusCode.Created;
            try {
                if (docId != null && docId.StartsWith("_local")) {
                    outRev = db.Storage.PutLocalRevision(rev, prevRevId, true); //TODO: Doesn't match iOS
                } else {
                    Status retStatus = new Status();
                    outRev = db.PutRevision(rev, prevRevId, allowConflict, retStatus);
                    status = retStatus.Code;
                }
            } catch(CouchbaseLiteException e) {
                status = e.Code;
            }

            return status;
        }
        public void LoadRevisionBody(RevisionInternal rev)
        {
            WithC4Document(rev.GetDocId(), rev.GetRevId(), true, false, doc => 
            {
                if(doc == null) {
                    throw new CouchbaseLiteException(StatusCode.NotFound);
                }

                rev.SetBody(new Body(doc->selectedRev.body));
            });
        }
        public void TestLoadRevisionBody()
        {
            var document = database.CreateDocument();
            var properties = new Dictionary<string, object>();
            properties["foo"] = "foo";
            properties["bar"] = false;
            properties["_id"] = document.Id;
            document.PutProperties(properties);
            properties.SetRevID(document.CurrentRevisionId);
            Assert.IsNotNull(document.CurrentRevision);

            var revisionInternal = new RevisionInternal(
                document.Id, document.CurrentRevisionId.AsRevID(), false);

            database.LoadRevisionBody(revisionInternal);
            Assert.AreEqual(properties, revisionInternal.GetProperties());
            revisionInternal.SetBody(null);

            // now lets purge the document, and then try to load the revision body again
            document.Purge();

            var gotExpectedException = false;
            try {
                database.LoadRevisionBody(revisionInternal);
            } catch (CouchbaseLiteException e) {
                gotExpectedException |= 
                    e.CBLStatus.Code == StatusCode.NotFound;
            }

            Assert.IsTrue(gotExpectedException);
        }
Пример #7
0
		public RevisionInternal UpdateAttachment(string filename, InputStream contentStream
			, string contentType, string docID, string oldRevID)
		{
			bool isSuccessful = false;
			if (filename == null || filename.Length == 0 || (contentStream != null && contentType
				 == null) || (oldRevID != null && docID == null) || (contentStream != null && docID
				 == null))
			{
				throw new CouchbaseLiteException(Status.BadRequest);
			}
			BeginTransaction();
			try
			{
				RevisionInternal oldRev = new RevisionInternal(docID, oldRevID, false, this);
				if (oldRevID != null)
				{
					// Load existing revision if this is a replacement:
					try
					{
						LoadRevisionBody(oldRev, EnumSet.NoneOf<Database.TDContentOptions>());
					}
					catch (CouchbaseLiteException e)
					{
						if (e.GetCBLStatus().GetCode() == Status.NotFound && ExistsDocumentWithIDAndRev(docID
							, null))
						{
							throw new CouchbaseLiteException(Status.Conflict);
						}
					}
					IDictionary<string, object> oldRevProps = oldRev.GetProperties();
					IDictionary<string, object> attachments = null;
					if (oldRevProps != null)
					{
						attachments = (IDictionary<string, object>)oldRevProps.Get("_attachments");
					}
					if (contentStream == null && attachments != null && !attachments.ContainsKey(filename
						))
					{
						throw new CouchbaseLiteException(Status.NotFound);
					}
					// Remove the _attachments stubs so putRevision: doesn't copy the rows for me
					// OPT: Would be better if I could tell loadRevisionBody: not to add it
					if (attachments != null)
					{
						IDictionary<string, object> properties = new Dictionary<string, object>(oldRev.GetProperties
							());
						Sharpen.Collections.Remove(properties, "_attachments");
						oldRev.SetBody(new Body(properties));
					}
				}
				else
				{
					// If this creates a new doc, it needs a body:
					oldRev.SetBody(new Body(new Dictionary<string, object>()));
				}
				// Create a new revision:
				Status putStatus = new Status();
				RevisionInternal newRev = PutRevision(oldRev, oldRevID, false, putStatus);
				if (newRev == null)
				{
					return null;
				}
				if (oldRevID != null)
				{
					// Copy all attachment rows _except_ for the one being updated:
					string[] args = new string[] { System.Convert.ToString(newRev.GetSequence()), System.Convert.ToString
						(oldRev.GetSequence()), filename };
					database.ExecSQL("INSERT INTO attachments " + "(sequence, filename, key, type, length, revpos) "
						 + "SELECT ?, filename, key, type, length, revpos FROM attachments " + "WHERE sequence=? AND filename != ?"
						, args);
				}
				if (contentStream != null)
				{
					// If not deleting, add a new attachment entry:
					InsertAttachmentForSequenceWithNameAndType(contentStream, newRev.GetSequence(), filename
						, contentType, newRev.GetGeneration());
				}
				isSuccessful = true;
				return newRev;
			}
			catch (SQLException e)
			{
				Log.E(Tag, "Error updating attachment", e);
				throw new CouchbaseLiteException(new Status(Status.InternalServerError));
			}
			finally
			{
				EndTransaction(isSuccessful);
			}
		}
        public void LoadRevisionBody(RevisionInternal rev)
        {
            WithC4Document(rev.DocID, rev.RevID, true, false, doc =>
            {
                if(doc == null) {
                    throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.NotFound, TAG,
                        "Cannot load revision body for non-existent revision {0}", rev);
                }

                rev.SetBody(new Body(doc->selectedRev.body));
            });
        }
        /// <summary>
        /// Attempt to update a document based on the information in the HTTP request
        /// </summary>
        /// <returns>The resulting status of the operation</returns>
        /// <param name="context">The request context</param>
        /// <param name="db">The database in which the document exists</param>
        /// <param name="docId">The ID of the document being updated</param>
        /// <param name="body">The new document body</param>
        /// <param name="deleting">Whether or not the document is being deleted</param>
        /// <param name="allowConflict">Whether or not to allow a conflict to be inserted</param>
        /// <param name="outRev">The resulting revision of the document</param>
        public static StatusCode UpdateDocument(ICouchbaseListenerContext context, Database db, string docId, Body body, bool deleting, 
            bool allowConflict, out RevisionInternal outRev)
        {
            outRev = null;
            if (body != null && !body.IsValidJSON()) {
                return StatusCode.BadJson;
            }

            string prevRevId;
            if (!deleting) {
                var properties = body.GetProperties();
                deleting = properties.GetCast<bool>("_deleted");
                if (docId == null) {
                    // POST's doc ID may come from the _id field of the JSON body.
                    docId = properties.CblID();
                    if (docId == null && deleting) {
                        return StatusCode.BadId;
                    }
                }

                // PUT's revision ID comes from the JSON body.
                prevRevId = properties.GetCast<string>("_rev");
            } else {
                // DELETE's revision ID comes from the ?rev= query param
                prevRevId = context.GetQueryParam("rev");
            }

            // A backup source of revision ID is an If-Match header:
            if (prevRevId == null) {
                prevRevId = context.IfMatch();
            }

            if (docId == null && deleting) {
                return StatusCode.BadId;
            }

            RevisionInternal rev = new RevisionInternal(docId, null, deleting);
            rev.SetBody(body);

            // Check for doc expiration
            var expirationTime = default(DateTime?);
            var tmp = default(object);
            var props = rev.GetProperties();
            var hasValue = false;
            if(props != null && props.TryGetValue("_exp", out tmp)) {
                hasValue = true;
                if(tmp != null) {
                    try {
                        expirationTime = Convert.ToDateTime(tmp);
                    } catch(Exception) {
                        try {
                            var num = Convert.ToInt64(tmp);
                            expirationTime = Misc.OffsetFromEpoch(TimeSpan.FromSeconds(num));
                        } catch(Exception) {
                            Log.To.Router.E(TAG, "Invalid value for _exp: {0}", tmp);
                            return StatusCode.BadRequest;
                        }

                    }
                }
            
                props.Remove("_exp");
                rev.SetProperties(props);
            }

            var castContext = context as ICouchbaseListenerContext2;
            var source = castContext != null && !castContext.IsLoopbackRequest ? castContext.Sender : null;
            StatusCode status = deleting ? StatusCode.Ok : StatusCode.Created;
            try {
                if(docId != null && docId.StartsWith("_local")) {
                    if(expirationTime.HasValue) {
                        return StatusCode.BadRequest;
                    }

                    outRev = db.Storage.PutLocalRevision(rev, prevRevId.AsRevID(), true); //TODO: Doesn't match iOS
                } else {
                    outRev = db.PutRevision(rev, prevRevId.AsRevID(), allowConflict, source);
                    if(hasValue) {
                        db.Storage?.SetDocumentExpiration(rev.DocID, expirationTime);
                    }
                }
            } catch(CouchbaseLiteException e) {
                status = e.Code;
            }

            return status;
        }
Пример #10
0
        /// <summary>Updates or deletes an attachment, creating a new document revision in the process.
        ///     </summary>
        /// <remarks>
        /// Updates or deletes an attachment, creating a new document revision in the process.
        /// Used by the PUT / DELETE methods called on attachment URLs.  Used by the listener;
        /// </remarks>
        internal RevisionInternal UpdateAttachment(string filename, BlobStoreWriter body, string contentType, AttachmentEncoding encoding, string docID, RevisionID oldRevID, Uri source)
        {
            if(StringEx.IsNullOrWhiteSpace(filename)) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadAttachment, TAG,
                    "Invalid filename (null or whitespace) in UpdateAttachment");
            }

            if(body != null && contentType == null) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadAttachment, TAG,
                    "Body provided, but content type is null in UpdateAttachment");
            }

            if(oldRevID != null && docID == null) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadAttachment, TAG,
                    "oldRevID provided ({0}) but docID is null in UpdateAttachment", oldRevID);
            }

            if(body != null && docID == null) {
                throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.BadAttachment, TAG,
                    "body provided but docID is null in UpdateAttachment");
            }

            var oldRev = new RevisionInternal(docID, oldRevID, false);
            if(oldRevID != null) {
                // Load existing revision if this is a replacement:
                try {
                    oldRev = LoadRevisionBody(oldRev);
                } catch(CouchbaseLiteException e) {
                    if(e.Code == StatusCode.NotFound && GetDocument(docID, null, false) != null) {
                        throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.Conflict, TAG,
                            "Conflict detected in UpdateAttachment");
                    }

                    Log.To.Database.E(TAG, "Error loading revision body in UpdateAttachment, rethrowing...");
                    throw;
                }
            } else {
                // If this creates a new doc, it needs a body:
                oldRev.SetBody(new Body(new Dictionary<string, object>()));
            }

            // Update the _attachments dictionary:
            var attachments = oldRev.GetProperties().Get("_attachments").AsDictionary<string, object>();
            if(attachments == null) {
                attachments = new Dictionary<string, object>();
            }

            if(body != null) {
                var key = body.GetBlobKey();
                string digest = key.Base64Digest();
                RememberAttachmentWriter(body, digest);
                string encodingName = (encoding == AttachmentEncoding.GZIP) ? "gzip" : null;
                attachments[filename] = new NonNullDictionary<string, object> {
                    { "digest", digest },
                    { "length", body.GetLength() },
                    { "follows", true },
                    { "content_type", contentType },
                    { "encoding", encodingName }
                };
            } else {
                if(oldRevID != null && attachments.Get(filename) == null) {
                    throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.AttachmentNotFound, TAG,
                        "Attachment {0} not found", new SecureLogString(filename, LogMessageSensitivity.PotentiallyInsecure));
                }

                attachments.Remove(filename);
            }

            var properties = oldRev.GetProperties();
            properties["_attachments"] = attachments;
            oldRev.SetProperties(properties);

            var newRev = PutRevision(oldRev, oldRevID, false, source);
            return newRev;
        }