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