public void TestStubOutAttachmentsInRevBeforeRevPos() { var hello = new JObject(); hello["revpos"] = 1; hello["follows"] = true; var goodbye = new JObject(); goodbye["revpos"] = 2; goodbye["data"] = "squeee"; var attachments = new JObject(); attachments["hello"] = hello; attachments["goodbye"] = goodbye; var properties = new Dictionary <string, object>(); properties["_attachments"] = attachments; IDictionary <string, object> expected = null; var rev = new RevisionInternal(properties); Database.StubOutAttachmentsInRevBeforeRevPos(rev, 3, false); var checkAttachments = rev.GetProperties()["_attachments"].AsDictionary <string, object>(); var result = (IDictionary <string, object>)checkAttachments["hello"]; expected = new Dictionary <string, object>(); expected["revpos"] = 1; expected["stub"] = true; AssertPropertiesAreEqual(expected, result); result = (IDictionary <string, object>)checkAttachments["goodbye"]; expected = new Dictionary <string, object>(); expected["revpos"] = 2; expected["stub"] = true; AssertPropertiesAreEqual(expected, result); rev = new RevisionInternal(properties); Database.StubOutAttachmentsInRevBeforeRevPos(rev, 2, false); checkAttachments = rev.GetProperties()["_attachments"].AsDictionary <string, object>(); result = checkAttachments["hello"].AsDictionary <string, object>(); expected = new Dictionary <string, object>(); expected["revpos"] = 1; expected["stub"] = true; AssertPropertiesAreEqual(expected, result); result = checkAttachments["goodbye"].AsDictionary <string, object>(); expected = goodbye.AsDictionary <string, object>(); AssertPropertiesAreEqual(expected, result); rev = new RevisionInternal(properties); Database.StubOutAttachmentsInRevBeforeRevPos(rev, 1, false); checkAttachments = rev.GetProperties()["_attachments"].AsDictionary <string, object>(); result = checkAttachments["hello"].AsDictionary <string, object>(); expected = hello.AsDictionary <string, object>(); AssertPropertiesAreEqual(expected, result); result = checkAttachments["goodbye"].AsDictionary <string, object>(); expected = goodbye.AsDictionary <string, object>(); AssertPropertiesAreEqual(expected, result); //Test the follows mode rev = new RevisionInternal(properties); Database.StubOutAttachmentsInRevBeforeRevPos(rev, 3, true); checkAttachments = rev.GetProperties()["_attachments"].AsDictionary <string, object>(); result = checkAttachments["hello"].AsDictionary <string, object>(); expected = new Dictionary <string, object>(); expected["revpos"] = 1; expected["stub"] = true; AssertPropertiesAreEqual(expected, result); result = checkAttachments["goodbye"].AsDictionary <string, object>(); expected = new Dictionary <string, object>(); expected["revpos"] = 2; expected["stub"] = true; AssertPropertiesAreEqual(expected, result); rev = new RevisionInternal(properties); Database.StubOutAttachmentsInRevBeforeRevPos(rev, 2, true); checkAttachments = rev.GetProperties()["_attachments"].AsDictionary <string, object>(); result = checkAttachments["hello"].AsDictionary <string, object>(); expected = new Dictionary <string, object>(); expected["revpos"] = 1; expected["stub"] = true; AssertPropertiesAreEqual(expected, result); result = checkAttachments["goodbye"].AsDictionary <string, object>(); expected = new Dictionary <string, object>(); expected["revpos"] = 2; expected["follows"] = true; AssertPropertiesAreEqual(expected, result); rev = new RevisionInternal(properties); Database.StubOutAttachmentsInRevBeforeRevPos(rev, 1, true); checkAttachments = rev.GetProperties()["_attachments"].AsDictionary <string, object>(); result = checkAttachments["hello"].AsDictionary <string, object>(); expected = new Dictionary <string, object>(); expected["revpos"] = 1; expected["follows"] = true; AssertPropertiesAreEqual(expected, result); result = checkAttachments["goodbye"].AsDictionary <string, object>(); expected = new Dictionary <string, object>(); expected["revpos"] = 2; expected["follows"] = true; AssertPropertiesAreEqual(expected, result); }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> public virtual void TestPutAttachment() { string testAttachmentName = "test_attachment"; BlobStore attachments = database.GetAttachments(); attachments.DeleteBlobs(); NUnit.Framework.Assert.AreEqual(0, attachments.Count()); // Put a revision that includes an _attachments dict: byte[] attach1 = Sharpen.Runtime.GetBytesForString("This is the body of attach1"); string base64 = Base64.EncodeBytes(attach1); IDictionary <string, object> attachment = new Dictionary <string, object>(); attachment.Put("content_type", "text/plain"); attachment.Put("data", base64); IDictionary <string, object> attachmentDict = new Dictionary <string, object>(); attachmentDict.Put(testAttachmentName, attachment); IDictionary <string, object> properties = new Dictionary <string, object>(); properties.Put("foo", 1); properties.Put("bar", false); properties.Put("_attachments", attachmentDict); RevisionInternal rev1 = database.PutRevision(new RevisionInternal(properties, database ), null, false); // Examine the attachment store: NUnit.Framework.Assert.AreEqual(1, attachments.Count()); // Get the revision: RevisionInternal gotRev1 = database.GetDocumentWithIDAndRev(rev1.GetDocId(), rev1 .GetRevId(), EnumSet.NoneOf <Database.TDContentOptions>()); IDictionary <string, object> gotAttachmentDict = (IDictionary <string, object>)gotRev1 .GetProperties().Get("_attachments"); IDictionary <string, object> innerDict = new Dictionary <string, object>(); innerDict.Put("content_type", "text/plain"); innerDict.Put("digest", "sha1-gOHUOBmIMoDCrMuGyaLWzf1hQTE="); innerDict.Put("length", 27); innerDict.Put("stub", true); innerDict.Put("revpos", 1); IDictionary <string, object> expectAttachmentDict = new Dictionary <string, object> (); expectAttachmentDict.Put(testAttachmentName, innerDict); NUnit.Framework.Assert.AreEqual(expectAttachmentDict, gotAttachmentDict); // Update the attachment directly: byte[] attachv2 = Sharpen.Runtime.GetBytesForString("Replaced body of attach"); bool gotExpectedErrorCode = false; try { database.UpdateAttachment(testAttachmentName, new ByteArrayInputStream(attachv2), "application/foo", rev1.GetDocId(), null); } catch (CouchbaseLiteException e) { gotExpectedErrorCode = (e.GetCBLStatus().GetCode() == Status.Conflict); } NUnit.Framework.Assert.IsTrue(gotExpectedErrorCode); gotExpectedErrorCode = false; try { database.UpdateAttachment(testAttachmentName, new ByteArrayInputStream(attachv2), "application/foo", rev1.GetDocId(), "1-bogus"); } catch (CouchbaseLiteException e) { gotExpectedErrorCode = (e.GetCBLStatus().GetCode() == Status.Conflict); } NUnit.Framework.Assert.IsTrue(gotExpectedErrorCode); gotExpectedErrorCode = false; RevisionInternal rev2 = null; try { rev2 = database.UpdateAttachment(testAttachmentName, new ByteArrayInputStream(attachv2 ), "application/foo", rev1.GetDocId(), rev1.GetRevId()); } catch (CouchbaseLiteException) { gotExpectedErrorCode = true; } NUnit.Framework.Assert.IsFalse(gotExpectedErrorCode); NUnit.Framework.Assert.AreEqual(rev1.GetDocId(), rev2.GetDocId()); NUnit.Framework.Assert.AreEqual(2, rev2.GetGeneration()); // Get the updated revision: RevisionInternal gotRev2 = database.GetDocumentWithIDAndRev(rev2.GetDocId(), rev2 .GetRevId(), EnumSet.NoneOf <Database.TDContentOptions>()); attachmentDict = (IDictionary <string, object>)gotRev2.GetProperties().Get("_attachments" ); innerDict = new Dictionary <string, object>(); innerDict.Put("content_type", "application/foo"); innerDict.Put("digest", "sha1-mbT3208HI3PZgbG4zYWbDW2HsPk="); innerDict.Put("length", 23); innerDict.Put("stub", true); innerDict.Put("revpos", 2); expectAttachmentDict.Put(testAttachmentName, innerDict); NUnit.Framework.Assert.AreEqual(expectAttachmentDict, attachmentDict); // Delete the attachment: gotExpectedErrorCode = false; try { database.UpdateAttachment("nosuchattach", null, null, rev2.GetDocId(), rev2.GetRevId ()); } catch (CouchbaseLiteException e) { gotExpectedErrorCode = (e.GetCBLStatus().GetCode() == Status.NotFound); } NUnit.Framework.Assert.IsTrue(gotExpectedErrorCode); gotExpectedErrorCode = false; try { database.UpdateAttachment("nosuchattach", null, null, "nosuchdoc", "nosuchrev"); } catch (CouchbaseLiteException e) { gotExpectedErrorCode = (e.GetCBLStatus().GetCode() == Status.NotFound); } NUnit.Framework.Assert.IsTrue(gotExpectedErrorCode); RevisionInternal rev3 = database.UpdateAttachment(testAttachmentName, null, null, rev2.GetDocId(), rev2.GetRevId()); NUnit.Framework.Assert.AreEqual(rev2.GetDocId(), rev3.GetDocId()); NUnit.Framework.Assert.AreEqual(3, rev3.GetGeneration()); // Get the updated revision: RevisionInternal gotRev3 = database.GetDocumentWithIDAndRev(rev3.GetDocId(), rev3 .GetRevId(), EnumSet.NoneOf <Database.TDContentOptions>()); attachmentDict = (IDictionary <string, object>)gotRev3.GetProperties().Get("_attachments" ); NUnit.Framework.Assert.IsNull(attachmentDict); database.Close(); }
/// <exception cref="System.Exception"></exception> public virtual void TestAttachments() { string testAttachmentName = "test_attachment"; BlobStore attachments = database.GetAttachments(); NUnit.Framework.Assert.AreEqual(0, attachments.Count()); NUnit.Framework.Assert.AreEqual(new HashSet <object>(), attachments.AllKeys()); Status status = new Status(); IDictionary <string, object> rev1Properties = new Dictionary <string, object>(); rev1Properties.Put("foo", 1); rev1Properties.Put("bar", false); RevisionInternal rev1 = database.PutRevision(new RevisionInternal(rev1Properties, database), null, false, status); NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode()); byte[] attach1 = Sharpen.Runtime.GetBytesForString("This is the body of attach1"); database.InsertAttachmentForSequenceWithNameAndType(new ByteArrayInputStream(attach1 ), rev1.GetSequence(), testAttachmentName, "text/plain", rev1.GetGeneration()); NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode()); Attachment attachment = database.GetAttachmentForSequence(rev1.GetSequence(), testAttachmentName ); NUnit.Framework.Assert.AreEqual("text/plain", attachment.GetContentType()); byte[] data = IOUtils.ToByteArray(attachment.GetContent()); NUnit.Framework.Assert.IsTrue(Arrays.Equals(attach1, data)); IDictionary <string, object> innerDict = new Dictionary <string, object>(); innerDict.Put("content_type", "text/plain"); innerDict.Put("digest", "sha1-gOHUOBmIMoDCrMuGyaLWzf1hQTE="); innerDict.Put("length", 27); innerDict.Put("stub", true); innerDict.Put("revpos", 1); IDictionary <string, object> attachmentDict = new Dictionary <string, object>(); attachmentDict.Put(testAttachmentName, innerDict); IDictionary <string, object> attachmentDictForSequence = database.GetAttachmentsDictForSequenceWithContent (rev1.GetSequence(), EnumSet.NoneOf <Database.TDContentOptions>()); NUnit.Framework.Assert.AreEqual(attachmentDict, attachmentDictForSequence); RevisionInternal gotRev1 = database.GetDocumentWithIDAndRev(rev1.GetDocId(), rev1 .GetRevId(), EnumSet.NoneOf <Database.TDContentOptions>()); IDictionary <string, object> gotAttachmentDict = (IDictionary <string, object>)gotRev1 .GetProperties().Get("_attachments"); NUnit.Framework.Assert.AreEqual(attachmentDict, gotAttachmentDict); // Check the attachment dict, with attachments included: Sharpen.Collections.Remove(innerDict, "stub"); innerDict.Put("data", Base64.EncodeBytes(attach1)); attachmentDictForSequence = database.GetAttachmentsDictForSequenceWithContent(rev1 .GetSequence(), EnumSet.Of(Database.TDContentOptions.TDIncludeAttachments)); NUnit.Framework.Assert.AreEqual(attachmentDict, attachmentDictForSequence); gotRev1 = database.GetDocumentWithIDAndRev(rev1.GetDocId(), rev1.GetRevId(), EnumSet .Of(Database.TDContentOptions.TDIncludeAttachments)); gotAttachmentDict = (IDictionary <string, object>)gotRev1.GetProperties().Get("_attachments" ); NUnit.Framework.Assert.AreEqual(attachmentDict, gotAttachmentDict); // Add a second revision that doesn't update the attachment: IDictionary <string, object> rev2Properties = new Dictionary <string, object>(); rev2Properties.Put("_id", rev1.GetDocId()); rev2Properties.Put("foo", 2); rev2Properties.Put("bazz", false); RevisionInternal rev2 = database.PutRevision(new RevisionInternal(rev2Properties, database), rev1.GetRevId(), false, status); NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode()); database.CopyAttachmentNamedFromSequenceToSequence(testAttachmentName, rev1.GetSequence (), rev2.GetSequence()); // Add a third revision of the same document: IDictionary <string, object> rev3Properties = new Dictionary <string, object>(); rev3Properties.Put("_id", rev2.GetDocId()); rev3Properties.Put("foo", 2); rev3Properties.Put("bazz", false); RevisionInternal rev3 = database.PutRevision(new RevisionInternal(rev3Properties, database), rev2.GetRevId(), false, status); NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode()); byte[] attach2 = Sharpen.Runtime.GetBytesForString("<html>And this is attach2</html>" ); database.InsertAttachmentForSequenceWithNameAndType(new ByteArrayInputStream(attach2 ), rev3.GetSequence(), testAttachmentName, "text/html", rev2.GetGeneration()); // Check the 2nd revision's attachment: Attachment attachment2 = database.GetAttachmentForSequence(rev2.GetSequence(), testAttachmentName ); NUnit.Framework.Assert.AreEqual("text/plain", attachment2.GetContentType()); data = IOUtils.ToByteArray(attachment2.GetContent()); NUnit.Framework.Assert.IsTrue(Arrays.Equals(attach1, data)); // Check the 3rd revision's attachment: Attachment attachment3 = database.GetAttachmentForSequence(rev3.GetSequence(), testAttachmentName ); NUnit.Framework.Assert.AreEqual("text/html", attachment3.GetContentType()); data = IOUtils.ToByteArray(attachment3.GetContent()); NUnit.Framework.Assert.IsTrue(Arrays.Equals(attach2, data)); // Examine the attachment store: NUnit.Framework.Assert.AreEqual(2, attachments.Count()); ICollection <BlobKey> expected = new HashSet <BlobKey>(); expected.AddItem(BlobStore.KeyForBlob(attach1)); expected.AddItem(BlobStore.KeyForBlob(attach2)); NUnit.Framework.Assert.AreEqual(expected, attachments.AllKeys()); database.Compact(); // This clears the body of the first revision NUnit.Framework.Assert.AreEqual(1, attachments.Count()); ICollection <BlobKey> expected2 = new HashSet <BlobKey>(); expected2.AddItem(BlobStore.KeyForBlob(attach2)); NUnit.Framework.Assert.AreEqual(expected2, attachments.AllKeys()); }
public void OnCompletion(object response, Exception e) { try { Log.V(Log.TagSync, "%s: got /_revs_diff response"); IDictionary <string, object> results = (IDictionary <string, object>)response; if (e != null) { this._enclosing.SetError(e); this._enclosing.RevisionFailed(); } else { if (results.Count != 0) { // Go through the list of local changes again, selecting the ones the destination server // said were missing and mapping them to a JSON dictionary in the form _bulk_docs wants: IList <object> docsToSend = new AList <object>(); RevisionList revsToSend = new RevisionList(); foreach (RevisionInternal rev in changes) { // Is this revision in the server's 'missing' list? IDictionary <string, object> properties = null; IDictionary <string, object> revResults = (IDictionary <string, object>)results.Get (rev.GetDocId()); if (revResults == null) { continue; } IList <string> revs = (IList <string>)revResults.Get("missing"); if (revs == null || !revs.Contains(rev.GetRevId())) { this._enclosing.RemovePending(rev); continue; } // Get the revision's properties: EnumSet <Database.TDContentOptions> contentOptions = EnumSet.Of(Database.TDContentOptions .TDIncludeAttachments); if (!this._enclosing.dontSendMultipart && this._enclosing.revisionBodyTransformationBlock == null) { contentOptions.AddItem(Database.TDContentOptions.TDBigAttachmentsFollow); } RevisionInternal loadedRev; try { loadedRev = this._enclosing.db.LoadRevisionBody(rev, contentOptions); properties = new Dictionary <string, object>(rev.GetProperties()); } catch (CouchbaseLiteException) { Log.W(Log.TagSync, "%s Couldn't get local contents of %s", rev, this._enclosing); this._enclosing.RevisionFailed(); continue; } RevisionInternal populatedRev = this._enclosing.TransformRevision(loadedRev); IList <string> possibleAncestors = (IList <string>)revResults.Get("possible_ancestors" ); properties = new Dictionary <string, object>(populatedRev.GetProperties()); IDictionary <string, object> revisions = this._enclosing.db.GetRevisionHistoryDictStartingFromAnyAncestor (populatedRev, possibleAncestors); properties.Put("_revisions", revisions); populatedRev.SetProperties(properties); // Strip any attachments already known to the target db: if (properties.ContainsKey("_attachments")) { // Look for the latest common ancestor and stub out older attachments: int minRevPos = Couchbase.Lite.Replicator.Pusher.FindCommonAncestor(populatedRev, possibleAncestors); Database.StubOutAttachmentsInRevBeforeRevPos(populatedRev, minRevPos + 1, false); properties = populatedRev.GetProperties(); if (!this._enclosing.dontSendMultipart && this._enclosing.UploadMultipartRevision (populatedRev)) { continue; } } if (properties == null || !properties.ContainsKey("_id")) { throw new InvalidOperationException("properties must contain a document _id"); } revsToSend.AddItem(rev); docsToSend.AddItem(properties); } //TODO: port this code from iOS // Post the revisions to the destination: this._enclosing.UploadBulkDocs(docsToSend, revsToSend); } else { // None of the revisions are new to the remote foreach (RevisionInternal revisionInternal in changes) { this._enclosing.RemovePending(revisionInternal); } } } } finally { Log.V(Log.TagSync, "%s | %s: processInbox.sendAsyncRequest() calling asyncTaskFinished()" , this, Sharpen.Thread.CurrentThread()); this._enclosing.AsyncTaskFinished(1); } }
/// <exception cref="System.Exception"></exception> public virtual void TestPutLargeAttachment() { string testAttachmentName = "test_attachment"; BlobStore attachments = database.GetAttachments(); attachments.DeleteBlobs(); NUnit.Framework.Assert.AreEqual(0, attachments.Count()); Status status = new Status(); IDictionary <string, object> rev1Properties = new Dictionary <string, object>(); rev1Properties.Put("foo", 1); rev1Properties.Put("bar", false); RevisionInternal rev1 = database.PutRevision(new RevisionInternal(rev1Properties, database), null, false, status); NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode()); StringBuilder largeAttachment = new StringBuilder(); for (int i = 0; i < Database.kBigAttachmentLength; i++) { largeAttachment.Append("big attachment!"); } byte[] attach1 = Sharpen.Runtime.GetBytesForString(largeAttachment.ToString()); database.InsertAttachmentForSequenceWithNameAndType(new ByteArrayInputStream(attach1 ), rev1.GetSequence(), testAttachmentName, "text/plain", rev1.GetGeneration()); Attachment attachment = database.GetAttachmentForSequence(rev1.GetSequence(), testAttachmentName ); NUnit.Framework.Assert.AreEqual("text/plain", attachment.GetContentType()); byte[] data = IOUtils.ToByteArray(attachment.GetContent()); NUnit.Framework.Assert.IsTrue(Arrays.Equals(attach1, data)); EnumSet <Database.TDContentOptions> contentOptions = EnumSet.Of(Database.TDContentOptions .TDIncludeAttachments, Database.TDContentOptions.TDBigAttachmentsFollow); IDictionary <string, object> attachmentDictForSequence = database.GetAttachmentsDictForSequenceWithContent (rev1.GetSequence(), contentOptions); IDictionary <string, object> innerDict = (IDictionary <string, object>)attachmentDictForSequence .Get(testAttachmentName); if (!innerDict.ContainsKey("stub")) { throw new RuntimeException("Expected attachment dict to have 'stub' key"); } if (((bool)innerDict.Get("stub")) == false) { throw new RuntimeException("Expected attachment dict 'stub' key to be true"); } if (!innerDict.ContainsKey("follows")) { throw new RuntimeException("Expected attachment dict to have 'follows' key"); } RevisionInternal rev1WithAttachments = database.GetDocumentWithIDAndRev(rev1.GetDocId (), rev1.GetRevId(), contentOptions); // Map<String,Object> rev1PropertiesPrime = rev1WithAttachments.getProperties(); // rev1PropertiesPrime.put("foo", 2); IDictionary <string, object> rev1WithAttachmentsProperties = rev1WithAttachments.GetProperties (); IDictionary <string, object> rev2Properties = new Dictionary <string, object>(); rev2Properties.Put("_id", rev1WithAttachmentsProperties.Get("_id")); rev2Properties.Put("foo", 2); RevisionInternal newRev = new RevisionInternal(rev2Properties, database); RevisionInternal rev2 = database.PutRevision(newRev, rev1WithAttachments.GetRevId (), false, status); NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode()); database.CopyAttachmentNamedFromSequenceToSequence(testAttachmentName, rev1WithAttachments .GetSequence(), rev2.GetSequence()); // Check the 2nd revision's attachment: Attachment rev2FetchedAttachment = database.GetAttachmentForSequence(rev2.GetSequence (), testAttachmentName); NUnit.Framework.Assert.AreEqual(attachment.GetLength(), rev2FetchedAttachment.GetLength ()); NUnit.Framework.Assert.AreEqual(attachment.GetMetadata(), rev2FetchedAttachment.GetMetadata ()); NUnit.Framework.Assert.AreEqual(attachment.GetContentType(), rev2FetchedAttachment .GetContentType()); // Add a third revision of the same document: IDictionary <string, object> rev3Properties = new Dictionary <string, object>(); rev3Properties.Put("_id", rev2.GetProperties().Get("_id")); rev3Properties.Put("foo", 3); rev3Properties.Put("baz", false); RevisionInternal rev3 = new RevisionInternal(rev3Properties, database); rev3 = database.PutRevision(rev3, rev2.GetRevId(), false, status); NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode()); byte[] attach3 = Sharpen.Runtime.GetBytesForString("<html><blink>attach3</blink></html>" ); database.InsertAttachmentForSequenceWithNameAndType(new ByteArrayInputStream(attach3 ), rev3.GetSequence(), testAttachmentName, "text/html", rev3.GetGeneration()); // Check the 3rd revision's attachment: Attachment rev3FetchedAttachment = database.GetAttachmentForSequence(rev3.GetSequence (), testAttachmentName); data = IOUtils.ToByteArray(rev3FetchedAttachment.GetContent()); NUnit.Framework.Assert.IsTrue(Arrays.Equals(attach3, data)); NUnit.Framework.Assert.AreEqual("text/html", rev3FetchedAttachment.GetContentType ()); // TODO: why doesn't this work? // Assert.assertEquals(attach3.length, rev3FetchedAttachment.getLength()); ICollection <BlobKey> blobKeys = database.GetAttachments().AllKeys(); NUnit.Framework.Assert.AreEqual(2, blobKeys.Count); database.Compact(); blobKeys = database.GetAttachments().AllKeys(); NUnit.Framework.Assert.AreEqual(1, blobKeys.Count); }
public virtual void TestPutAttachment() { const string testAttachmentName = "test_attachment"; var attachments = database.Attachments; attachments.DeleteBlobs(); Assert.AreEqual(0, attachments.Count()); // Put a revision that includes an _attachments dict: var attach1 = Encoding.UTF8.GetBytes("This is the body of attach1"); var base64 = Convert.ToBase64String(attach1); var attachment = new Dictionary <string, object>(); attachment["content_type"] = "text/plain"; attachment["data"] = base64; IDictionary <string, object> attachmentDict = new Dictionary <string, object>(); attachmentDict[testAttachmentName] = attachment; var properties = new Dictionary <string, object>(); properties["foo"] = 1; properties["bar"] = false; properties["_attachments"] = attachmentDict; var rev1 = database.PutRevision(new RevisionInternal(properties), null, false); // Examine the attachment store: Assert.AreEqual(1, attachments.Count()); // Get the revision: var gotRev1 = database.GetDocument(rev1.GetDocId(), rev1.GetRevId(), true); var gotAttachmentDict = gotRev1.GetPropertyForKey("_attachments").AsDictionary <string, object>(); gotAttachmentDict[testAttachmentName] = gotAttachmentDict[testAttachmentName].AsDictionary <string, object>(); var innerDict = new Dictionary <string, object>(); innerDict["content_type"] = "text/plain"; innerDict["digest"] = "sha1-gOHUOBmIMoDCrMuGyaLWzf1hQTE="; innerDict["length"] = 27; innerDict["stub"] = true; innerDict["revpos"] = 1; var expectAttachmentDict = new Dictionary <string, object>(); expectAttachmentDict[testAttachmentName] = innerDict; Assert.AreEqual(expectAttachmentDict, gotAttachmentDict); // Update the attachment directly: var attachv2 = Encoding.UTF8.GetBytes("Replaced body of attach"); var writer = new BlobStoreWriter(database.Attachments); writer.AppendData(attachv2); writer.Finish(); var gotExpectedErrorCode = false; try { database.UpdateAttachment(testAttachmentName, writer, "application/foo", AttachmentEncoding.None, rev1.GetDocId(), null); } catch (CouchbaseLiteException e) { gotExpectedErrorCode = (e.CBLStatus.Code == StatusCode.Conflict); } Assert.IsTrue(gotExpectedErrorCode); gotExpectedErrorCode = false; try { database.UpdateAttachment(testAttachmentName, new BlobStoreWriter(database.Attachments), "application/foo", AttachmentEncoding.None, rev1.GetDocId(), "1-fafafa"); } catch (CouchbaseLiteException e) { gotExpectedErrorCode = (e.CBLStatus.Code == StatusCode.Conflict); } Assert.IsTrue(gotExpectedErrorCode); gotExpectedErrorCode = false; RevisionInternal rev2 = null; try { rev2 = database.UpdateAttachment(testAttachmentName, writer, "application/foo", AttachmentEncoding.None, rev1.GetDocId(), rev1.GetRevId()); } catch (CouchbaseLiteException) { gotExpectedErrorCode = true; } Assert.IsFalse(gotExpectedErrorCode); Assert.AreEqual(rev1.GetDocId(), rev2.GetDocId()); Assert.AreEqual(2, rev2.GetGeneration()); // Get the updated revision: RevisionInternal gotRev2 = database.GetDocument(rev2.GetDocId(), rev2 .GetRevId(), true); attachmentDict = gotRev2.GetProperties().Get("_attachments").AsDictionary <string, object>(); attachmentDict[testAttachmentName] = attachmentDict[testAttachmentName].AsDictionary <string, object>(); innerDict = new Dictionary <string, object>(); innerDict["content_type"] = "application/foo"; innerDict["digest"] = "sha1-mbT3208HI3PZgbG4zYWbDW2HsPk="; innerDict["length"] = 23; innerDict["stub"] = true; innerDict["revpos"] = 2; expectAttachmentDict[testAttachmentName] = innerDict; Assert.AreEqual(expectAttachmentDict, attachmentDict); // Delete the attachment: gotExpectedErrorCode = false; try { database.UpdateAttachment("nosuchattach", null, "application/foo", AttachmentEncoding.None, rev2.GetDocId(), rev2.GetRevId()); } catch (CouchbaseLiteException e) { gotExpectedErrorCode = (e.CBLStatus.Code == StatusCode.AttachmentNotFound); } Assert.IsTrue(gotExpectedErrorCode); gotExpectedErrorCode = false; try { database.UpdateAttachment("nosuchattach", null, null, AttachmentEncoding.None, "nosuchdoc", "nosuchrev"); } catch (CouchbaseLiteException e) { gotExpectedErrorCode = (e.CBLStatus.Code == StatusCode.NotFound); } Assert.IsTrue(gotExpectedErrorCode); RevisionInternal rev3 = database.UpdateAttachment(testAttachmentName, null, null, AttachmentEncoding.None, rev2.GetDocId(), rev2.GetRevId()); Assert.AreEqual(rev2.GetDocId(), rev3.GetDocId()); Assert.AreEqual(3, rev3.GetGeneration()); // Get the updated revision: RevisionInternal gotRev3 = database.GetDocument(rev3.GetDocId(), rev3 .GetRevId(), true); attachmentDict = gotRev3.GetProperties().Get("_attachments").AsDictionary <string, object>(); Assert.IsNull(attachmentDict); database.Close(); }
// Apply the options in the URL query to the specified revision and create a new revision object internal static RevisionInternal ApplyOptions(DocumentContentOptions options, RevisionInternal rev, ICouchbaseListenerContext context, Database db, Status outStatus) { if ((options & (DocumentContentOptions.IncludeRevs | DocumentContentOptions.IncludeRevsInfo | DocumentContentOptions.IncludeConflicts | DocumentContentOptions.IncludeAttachments | DocumentContentOptions.IncludeLocalSeq) | DocumentContentOptions.IncludeExpiration) != 0) { var dst = rev.GetProperties() ?? new Dictionary <string, object>(); if (options.HasFlag(DocumentContentOptions.IncludeLocalSeq)) { dst["_local_seq"] = rev.Sequence; } if (options.HasFlag(DocumentContentOptions.IncludeRevs)) { var revs = db.GetRevisionHistory(rev, null); dst["_revisions"] = TreeRevisionID.MakeRevisionHistoryDict(revs); } if (options.HasFlag(DocumentContentOptions.IncludeRevsInfo)) { dst["_revs_info"] = db.GetRevisionHistory(rev, null).Select(x => { string status = "available"; var ancestor = db.GetDocument(rev.DocID, x, true); if (ancestor.Deleted) { status = "deleted"; } else if (ancestor.Missing) { status = "missing"; } return(new Dictionary <string, object> { { "rev", x.ToString() }, { "status", status } }); }); } if (options.HasFlag(DocumentContentOptions.IncludeConflicts)) { RevisionList revs = db.Storage.GetAllDocumentRevisions(rev.DocID, true, false); if (revs.Count > 1) { dst["_conflicts"] = from r in revs where !r.Equals(rev) && !r.Deleted select r.RevID.ToString(); } } if (options.HasFlag(DocumentContentOptions.IncludeExpiration)) { var expirationTime = db.Storage?.GetDocumentExpiration(rev.DocID); if (expirationTime.HasValue) { dst["_exp"] = expirationTime; } } RevisionInternal nuRev = new RevisionInternal(dst); if (options.HasFlag(DocumentContentOptions.IncludeAttachments)) { bool attEncodingInfo = context != null && context.GetQueryParam <bool>("att_encoding_info", bool.TryParse, false); db.ExpandAttachments(nuRev, 0, false, !attEncodingInfo); } rev = nuRev; } return(rev); }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> public virtual void TestCRUDOperations() { database.AddChangeListener(this); string privateUUID = database.PrivateUUID(); string publicUUID = database.PublicUUID(); Log.V(Tag, "DB private UUID = '" + privateUUID + "', public UUID = '" + publicUUID + "'"); NUnit.Framework.Assert.IsTrue(privateUUID.Length >= 20); NUnit.Framework.Assert.IsTrue(publicUUID.Length >= 20); //create a document IDictionary <string, object> documentProperties = new Dictionary <string, object>(); documentProperties.Put("foo", 1); documentProperties.Put("bar", false); documentProperties.Put("baz", "touch"); Body body = new Body(documentProperties); RevisionInternal rev1 = new RevisionInternal(body, database); Status status = new Status(); rev1 = database.PutRevision(rev1, null, false, status); Log.V(Tag, "Created " + rev1); NUnit.Framework.Assert.IsTrue(rev1.GetDocId().Length >= 10); NUnit.Framework.Assert.IsTrue(rev1.GetRevId().StartsWith("1-")); //read it back RevisionInternal readRev = database.GetDocumentWithIDAndRev(rev1.GetDocId(), null , EnumSet.NoneOf <Database.TDContentOptions>()); NUnit.Framework.Assert.IsNotNull(readRev); IDictionary <string, object> readRevProps = readRev.GetProperties(); NUnit.Framework.Assert.AreEqual(UserProperties(readRevProps), UserProperties(body .GetProperties())); //now update it documentProperties = readRev.GetProperties(); documentProperties.Put("status", "updated!"); body = new Body(documentProperties); RevisionInternal rev2 = new RevisionInternal(body, database); RevisionInternal rev2input = rev2; rev2 = database.PutRevision(rev2, rev1.GetRevId(), false, status); Log.V(Tag, "Updated " + rev1); NUnit.Framework.Assert.AreEqual(rev1.GetDocId(), rev2.GetDocId()); NUnit.Framework.Assert.IsTrue(rev2.GetRevId().StartsWith("2-")); //read it back readRev = database.GetDocumentWithIDAndRev(rev2.GetDocId(), null, EnumSet.NoneOf < Database.TDContentOptions>()); NUnit.Framework.Assert.IsNotNull(readRev); NUnit.Framework.Assert.AreEqual(UserProperties(readRev.GetProperties()), UserProperties (body.GetProperties())); // Try to update the first rev, which should fail: bool gotExpectedError = false; try { database.PutRevision(rev2input, rev1.GetRevId(), false, status); } catch (CouchbaseLiteException e) { gotExpectedError = e.GetCBLStatus().GetCode() == Status.Conflict; } NUnit.Framework.Assert.IsTrue(gotExpectedError); // Check the changes feed, with and without filters: RevisionList changes = database.ChangesSince(0, null, null); Log.V(Tag, "Changes = " + changes); NUnit.Framework.Assert.AreEqual(1, changes.Count); changes = database.ChangesSince(0, null, new _ReplicationFilter_95()); NUnit.Framework.Assert.AreEqual(1, changes.Count); changes = database.ChangesSince(0, null, new _ReplicationFilter_105()); NUnit.Framework.Assert.AreEqual(0, changes.Count); // Delete it: RevisionInternal revD = new RevisionInternal(rev2.GetDocId(), null, true, database ); RevisionInternal revResult = null; gotExpectedError = false; try { revResult = database.PutRevision(revD, null, false, status); } catch (CouchbaseLiteException e) { gotExpectedError = e.GetCBLStatus().GetCode() == Status.Conflict; } NUnit.Framework.Assert.IsTrue(gotExpectedError); NUnit.Framework.Assert.IsNull(revResult); revD = database.PutRevision(revD, rev2.GetRevId(), false, status); NUnit.Framework.Assert.AreEqual(Status.Ok, status.GetCode()); NUnit.Framework.Assert.AreEqual(revD.GetDocId(), rev2.GetDocId()); NUnit.Framework.Assert.IsTrue(revD.GetRevId().StartsWith("3-")); // Delete nonexistent doc: RevisionInternal revFake = new RevisionInternal("fake", null, true, database); gotExpectedError = false; try { database.PutRevision(revFake, null, false, status); } catch (CouchbaseLiteException e) { gotExpectedError = e.GetCBLStatus().GetCode() == Status.NotFound; } NUnit.Framework.Assert.IsTrue(gotExpectedError); // Read it back (should fail): readRev = database.GetDocumentWithIDAndRev(revD.GetDocId(), null, EnumSet.NoneOf < Database.TDContentOptions>()); NUnit.Framework.Assert.IsNull(readRev); // Get Changes feed changes = database.ChangesSince(0, null, null); NUnit.Framework.Assert.IsTrue(changes.Count == 1); // Get Revision History IList <RevisionInternal> history = database.GetRevisionHistory(revD); NUnit.Framework.Assert.AreEqual(revD, history[0]); NUnit.Framework.Assert.AreEqual(rev2, history[1]); NUnit.Framework.Assert.AreEqual(rev1, history[2]); }
private bool UploadMultipartRevision(RevisionInternal revision) { MultipartContent multiPart = null; var revProps = revision.GetProperties(); var attachments = revProps.Get("_attachments").AsDictionary <string, object>(); foreach (var attachmentKey in attachments.Keys) { var attachment = attachments.Get(attachmentKey).AsDictionary <string, object>(); if (attachment.ContainsKey("follows")) { if (multiPart == null) { multiPart = new MultipartContent("related"); try { var json = Manager.GetObjectMapper().WriteValueAsString(revProps); var utf8charset = Encoding.UTF8; //multiPart.Add(new StringContent(json, utf8charset, "application/json"), "param1"); var jsonContent = new StringContent(json, utf8charset, "application/json"); //jsonContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"); multiPart.Add(jsonContent); } catch (IOException e) { throw new ArgumentException("Not able to serialize revision properties into a multipart request content.", e); } } var blobStore = LocalDatabase.Attachments; var base64Digest = (string)attachment.Get("digest"); var blobKey = new BlobKey(base64Digest); var inputStream = blobStore.BlobStreamForKey(blobKey); if (inputStream == null) { Log.W(Tag, "Unable to find blob file for blobKey: " + blobKey + " - Skipping upload of multipart revision."); multiPart = null; } else { string contentType = null; if (attachment.ContainsKey("content_type")) { contentType = (string)attachment.Get("content_type"); } else { if (attachment.ContainsKey("content-type")) { var message = string.Format("Found attachment that uses content-type" + " field name instead of content_type (see couchbase-lite-android" + " issue #80): " + attachment); Log.W(Tag, message); } } var content = new StreamContent(inputStream); content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = Path.GetFileName(blobStore.PathForKey(blobKey)) }; content.Headers.ContentType = new MediaTypeHeaderValue(contentType); multiPart.Add(content); } } } if (multiPart == null) { return(false); } var path = string.Format("/{0}?new_edits=false", revision.GetDocId()); // TODO: need to throttle these requests Log.D(Tag, "Uploading multipart request. Revision: " + revision); Log.D(Tag, "uploadMultipartRevision() calling asyncTaskStarted()"); SafeAddToChangesCount(1); AsyncTaskStarted(); SendAsyncMultipartRequest(HttpMethod.Put, path, multiPart, (result, e) => { try { if (e != null) { var httpError = e as HttpResponseException; if (httpError != null) { if (httpError.StatusCode == System.Net.HttpStatusCode.UnsupportedMediaType) { dontSendMultipart = true; UploadJsonRevision(revision); } } else { Log.E(Tag, "Exception uploading multipart request", e); SetLastError(e); RevisionFailed(); } } else { Log.D(Tag, "Uploaded multipart request. Result: " + result); RemovePending(revision); } } finally { Log.D(Tag, "uploadMultipartRevision() calling asyncTaskFinished()"); // TODO: calling addToCompleteChangesCount(1) AsyncTaskFinished(1); } }); return(true); }
public IList <QueryRow> QueryWithOptions(QueryOptions options) { if (options == null) { options = new QueryOptions(); } Cursor cursor = null; IList <QueryRow> rows = new AList <QueryRow>(); try { cursor = ResultSetWithOptions(options); int groupLevel = options.GetGroupLevel(); bool group = options.IsGroup() || (groupLevel > 0); bool reduce = options.IsReduce() || group; if (reduce && (reduceBlock == null) && !group) { string msg = "Cannot use reduce option in view " + name + " which has no reduce block defined"; Log.W(Database.Tag, msg); throw new CouchbaseLiteException(new Status(Status.BadRequest)); } if (reduce || group) { // Reduced or grouped query: rows = ReducedQuery(cursor, group, groupLevel); } else { // regular query cursor.MoveToNext(); while (!cursor.IsAfterLast()) { object keyData = FromJSON(cursor.GetBlob(0)); // TODO: delay parsing this for increased efficiency object value = FromJSON(cursor.GetBlob(1)); // TODO: ditto string docId = cursor.GetString(2); int sequence = Sharpen.Extensions.ValueOf(cursor.GetString(3)); IDictionary <string, object> docContents = null; if (options.IsIncludeDocs()) { // http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views#Linked_documents if (value is IDictionary && ((IDictionary)value).ContainsKey("_id")) { string linkedDocId = (string)((IDictionary)value).Get("_id"); RevisionInternal linkedDoc = database.GetDocumentWithIDAndRev(linkedDocId, null, EnumSet.NoneOf <Database.TDContentOptions>()); docContents = linkedDoc.GetProperties(); } else { docContents = database.DocumentPropertiesFromJSON(cursor.GetBlob(5), docId, cursor .GetString(4), false, cursor.GetLong(3), options.GetContentOptions()); } } QueryRow row = new QueryRow(docId, sequence, keyData, value, docContents); row.SetDatabase(database); rows.AddItem(row); cursor.MoveToNext(); } } } catch (SQLException e) { string errMsg = string.Format("Error querying view: %s", this); Log.E(Database.Tag, errMsg, e); throw new CouchbaseLiteException(errMsg, e, new Status(Status.DbError)); } finally { if (cursor != null) { cursor.Close(); } } return(rows); }
private bool UploadMultipartRevision(RevisionInternal revision) { MultipartFormDataContent multiPart = null; var revProps = revision.GetProperties(); revProps.Put("_revisions", LocalDatabase.GetRevisionHistoryDict(revision)); var attachments = (IDictionary <string, object>)revProps.Get("_attachments"); foreach (var attachmentKey in attachments.Keys) { var attachment = (IDictionary <String, Object>)attachments.Get(attachmentKey); if (attachment.ContainsKey("follows")) { if (multiPart == null) { multiPart = new MultipartFormDataContent(); try { var json = Manager.GetObjectMapper().WriteValueAsString(revProps); var utf8charset = Encoding.UTF8; multiPart.Add(new StringContent(json, utf8charset, "application/json"), "param1"); } catch (IOException e) { throw new ArgumentException("Not able to serialize revision properties into a multipart request content.", e); } } var blobStore = LocalDatabase.Attachments; var base64Digest = (string)attachment.Get("digest"); var blobKey = new BlobKey(base64Digest); var inputStream = blobStore.BlobStreamForKey(blobKey); if (inputStream == null) { Log.W(Tag, "Unable to find blob file for blobKey: " + blobKey + " - Skipping upload of multipart revision."); multiPart = null; } else { string contentType = null; if (attachment.ContainsKey("content_type")) { contentType = (string)attachment.Get("content_type"); } else { if (attachment.ContainsKey("content-type")) { var message = string.Format("Found attachment that uses content-type" + " field name instead of content_type (see couchbase-lite-android" + " issue #80): " + attachment); Log.W(Tag, message); } } var content = new StreamContent(inputStream); content.Headers.ContentType = new MediaTypeHeaderValue(contentType); multiPart.Add(content, attachmentKey); } } } if (multiPart == null) { return(false); } var path = string.Format("/{0}?new_edits=false", revision.GetDocId()); // TODO: need to throttle these requests Log.D(Tag, "Uploadeding multipart request. Revision: " + revision); Log.D(Tag, this + "|" + Thread.CurrentThread() + ": uploadMultipartRevision() calling asyncTaskStarted()"); AsyncTaskStarted(); SendAsyncMultipartRequest(HttpMethod.Put, path, multiPart, (result, e) => { try { if (e != null) { Log.E(Tag, "Exception uploading multipart request", e); LastError = e; RevisionFailed(); } else { Log.D(Tag, "Uploaded multipart request. Result: " + result); } } finally { AsyncTaskFinished(1); } }); return(true); }
// This invokes the tranformation block if one is installed and queues the resulting CBL_Revision private void QueueDownloadedRevision(RevisionInternal rev) { if (revisionBodyTransformationBlock != null) { // Add 'file' properties to attachments pointing to their bodies: foreach (KeyValuePair <string, IDictionary <string, object> > entry in ((IDictionary <string, IDictionary <string, object> >)rev.GetProperties().Get("_attachments")).EntrySet ()) { string name = entry.Key; IDictionary <string, object> attachment = entry.Value; Sharpen.Collections.Remove(attachment, "file"); if (attachment.Get("follows") != null && attachment.Get("data") == null) { string filePath = db.FileForAttachmentDict(attachment).AbsolutePath; if (filePath != null) { attachment.Put("file", filePath); } } } RevisionInternal xformed = TransformRevision(rev); if (xformed == null) { Log.V(Log.TagSync, "%s: Transformer rejected revision %s", this, rev); pendingSequences.RemoveSequence(rev.GetSequence()); lastSequence = pendingSequences.GetCheckpointedValue(); return; } rev = xformed; // Clean up afterwards IDictionary <string, object> attachments = (IDictionary <string, object>)rev.GetProperties ().Get("_attachments"); foreach (KeyValuePair <string, IDictionary <string, object> > entry_1 in ((IDictionary <string, IDictionary <string, object> >)rev.GetProperties().Get("_attachments")).EntrySet ()) { IDictionary <string, object> attachment = entry_1.Value; Sharpen.Collections.Remove(attachment, "file"); } } //TODO: rev.getBody().compact(); Log.V(Log.TagSync, "%s | %s: queueDownloadedRevision() calling asyncTaskStarted()" , this, Sharpen.Thread.CurrentThread()); AsyncTaskStarted(); downloadsToInsert.QueueObject(rev); }
public bool UpdateIndexes(IEnumerable <IViewStore> views) { Log.D(TAG, "Checking indexes of ({0}) for {1}", ViewNames(views), Name); // Creates an array of tuples -> [[view1, view1 last sequence, view1 native handle], // [view2, view2 last sequence, view2 native handle], ...] var viewsArray = views.Cast <ForestDBViewStore>().ToArray(); var viewInfo = viewsArray.Select(x => Tuple.Create(x, x.LastSequenceIndexed)).ToArray(); var nativeViews = new C4View *[viewsArray.Length]; for (int i = 0; i < viewsArray.Length; i++) { nativeViews[i] = viewsArray[i]._indexDB; } var indexer = (C4Indexer *)ForestDBBridge.Check(err => Native.c4indexer_begin(_dbStorage.Forest, nativeViews, err)); var enumerator = new CBForestDocEnumerator(indexer); var commit = false; try { foreach (var next in enumerator) { var seq = next.Sequence; for (int i = 0; i < viewInfo.Length; i++) { var info = viewInfo[i]; if (seq <= info.Item2) { continue; // This view has already indexed this sequence } var viewDelegate = info.Item1.Delegate; if (viewDelegate == null || viewDelegate.Map == null) { Log.V(TAG, " {0} has no map block; skipping it", info.Item1.Name); continue; } var rev = new RevisionInternal(next, true); var keys = new List <object>(); var values = new List <string>(); var conflicts = default(List <string>); foreach (var leaf in new CBForestHistoryEnumerator(next, true, false)) { if (leaf.SelectedRev.revID.Equals(leaf.CurrentRevID)) { continue; } if (leaf.IsDeleted) { break; } if (conflicts == null) { conflicts = new List <string>(); } conflicts.Add((string)leaf.SelectedRev.revID); } if (conflicts != null) { rev.SetPropertyForKey("_conflicts", conflicts); } try { viewDelegate.Map(rev.GetProperties(), (key, value) => { keys.Add(key); values.Add(Manager.GetObjectMapper().WriteValueAsString(value)); }); } catch (Exception e) { Log.W(TAG, String.Format("Exception thrown in map function of {0}", info.Item1.Name), e); continue; } WithC4Keys(keys.ToArray(), true, c4keys => ForestDBBridge.Check(err => Native.c4indexer_emit(indexer, next.GetDocument(), (uint)i, c4keys, values.ToArray(), err)) ); } } commit = true; } catch (Exception e) { Log.W(TAG, "Error updates indexes", e); } finally { ForestDBBridge.Check(err => Native.c4indexer_end(indexer, commit, err)); } return(true); }
private bool UploadMultipartRevision(RevisionInternal revision) { MultipartEntity multiPart = null; IDictionary <string, object> revProps = revision.GetProperties(); // TODO: refactor this to IDictionary <string, object> attachments = (IDictionary <string, object>)revProps.Get ("_attachments"); foreach (string attachmentKey in attachments.Keys) { IDictionary <string, object> attachment = (IDictionary <string, object>)attachments .Get(attachmentKey); if (attachment.ContainsKey("follows")) { if (multiPart == null) { multiPart = new MultipartEntity(); try { string json = Manager.GetObjectMapper().WriteValueAsString(revProps); Encoding utf8charset = Sharpen.Extensions.GetEncoding("UTF-8"); multiPart.AddPart("param1", new StringBody(json, "application/json", utf8charset) ); } catch (IOException e) { throw new ArgumentException(e); } } BlobStore blobStore = this.db.GetAttachments(); string base64Digest = (string)attachment.Get("digest"); BlobKey blobKey = new BlobKey(base64Digest); InputStream inputStream = blobStore.BlobStreamForKey(blobKey); if (inputStream == null) { Log.W(Log.TagSync, "Unable to find blob file for blobKey: %s - Skipping upload of multipart revision." , blobKey); multiPart = null; } else { string contentType = null; if (attachment.ContainsKey("content_type")) { contentType = (string)attachment.Get("content_type"); } else { if (attachment.ContainsKey("content-type")) { Log.W(Log.TagSync, "Found attachment that uses content-type" + " field name instead of content_type (see couchbase-lite-android" + " issue #80): %s", attachment); } } multiPart.AddPart(attachmentKey, new InputStreamBody(inputStream, contentType, attachmentKey )); } } } if (multiPart == null) { return(false); } string path = string.Format("/%s?new_edits=false", revision.GetDocId()); Log.D(Log.TagSync, "Uploading multipart request. Revision: %s", revision); AddToChangesCount(1); Log.V(Log.TagSync, "%s | %s: uploadMultipartRevision() calling asyncTaskStarted()" , this, Sharpen.Thread.CurrentThread()); AsyncTaskStarted(); SendAsyncMultipartRequest("PUT", path, multiPart, new _RemoteRequestCompletionBlock_542 (this, revision)); // Server doesn't like multipart, eh? Fall back to JSON. //status 415 = "bad_content_type" return(true); }
/// <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"); DocumentContentOptions options = context.ContentOptions; string openRevsParam = context.GetQueryParam("open_revs"); bool mustSendJson = context.ExplicitlyAcceptsType("application/json"); if (openRevsParam == null || isLocalDoc) { //Regular GET: string revId = context.GetQueryParam("rev"); //often null RevisionInternal rev; bool includeAttachments = false, sendMultipart = false; if (isLocalDoc) { rev = db.GetLocalDocument(docId, revId); } else { includeAttachments = options.HasFlag(DocumentContentOptions.IncludeAttachments); if (includeAttachments) { sendMultipart = !mustSendJson; options &= ~DocumentContentOptions.IncludeAttachments; } Status status = new Status(); rev = db.GetDocumentWithIDAndRev(docId, revId, options, status); if (rev != null) { rev = ApplyOptions(options, rev, context, db, status); } if (rev == null) { if (status.GetCode() == StatusCode.Deleted) { response.StatusReason = "deleted"; } else { response.StatusReason = "missing"; } response.InternalStatus = status.GetCode(); return response; } } if (rev == null) { response.InternalStatus = StatusCode.NotFound; return response; } if (context.CacheWithEtag(rev.GetRevId())) { response.InternalStatus = StatusCode.NotModified; return response; } if (!isLocalDoc && includeAttachments) { int minRevPos = 1; IList <string> attsSince = context.GetJsonQueryParam("atts_since").AsList <string>(); string ancestorId = db.FindCommonAncestor(rev, attsSince); if (ancestorId != null) { minRevPos = RevisionInternal.GenerationFromRevID(ancestorId) + 1; } Status status = new Status(); bool attEncodingInfo = context.GetQueryParam <bool>("att_encoding_info", bool.TryParse, false); if (!db.ExpandAttachments(rev, minRevPos, sendMultipart, attEncodingInfo, status)) { response.InternalStatus = status.GetCode(); return response; } } if (sendMultipart) { response.MultipartWriter = db.MultipartWriterForRev(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.GetAllRevisionsOfDocumentID(docId, true); result = new List <IDictionary <string, object> >(); foreach (var rev in allRevs) { if (!includeDeleted && rev.IsDeleted()) { continue; } Status status = new Status(); RevisionInternal 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.GetCode() <= StatusCode.InternalServerError) { result.Add(new Dictionary <string, object> { { "missing", rev.GetRevId() } }); } else { response.InternalStatus = status.GetCode(); 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; } Status status = new Status(); var rev = db.GetDocumentWithIDAndRev(docId, revID, DocumentContentOptions.None, status); 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()); }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> public virtual void TestLocalDocs() { //create a document IDictionary <string, object> documentProperties = new Dictionary <string, object>(); documentProperties.Put("_id", "_local/doc1"); documentProperties.Put("foo", 1); documentProperties.Put("bar", false); Body body = new Body(documentProperties); RevisionInternal rev1 = new RevisionInternal(body, database); Status status = new Status(); rev1 = database.PutLocalRevision(rev1, null); Log.V(Tag, "Created " + rev1); NUnit.Framework.Assert.AreEqual("_local/doc1", rev1.GetDocId()); NUnit.Framework.Assert.IsTrue(rev1.GetRevId().StartsWith("1-")); //read it back RevisionInternal readRev = database.GetLocalDocument(rev1.GetDocId(), null); NUnit.Framework.Assert.IsNotNull(readRev); IDictionary <string, object> readRevProps = readRev.GetProperties(); NUnit.Framework.Assert.AreEqual(rev1.GetDocId(), readRev.GetProperties().Get("_id" )); NUnit.Framework.Assert.AreEqual(rev1.GetRevId(), readRev.GetProperties().Get("_rev" )); NUnit.Framework.Assert.AreEqual(UserProperties(readRevProps), UserProperties(body .GetProperties())); //now update it documentProperties = readRev.GetProperties(); documentProperties.Put("status", "updated!"); body = new Body(documentProperties); RevisionInternal rev2 = new RevisionInternal(body, database); RevisionInternal rev2input = rev2; rev2 = database.PutLocalRevision(rev2, rev1.GetRevId()); Log.V(Tag, "Updated " + rev1); NUnit.Framework.Assert.AreEqual(rev1.GetDocId(), rev2.GetDocId()); NUnit.Framework.Assert.IsTrue(rev2.GetRevId().StartsWith("2-")); //read it back readRev = database.GetLocalDocument(rev2.GetDocId(), null); NUnit.Framework.Assert.IsNotNull(readRev); NUnit.Framework.Assert.AreEqual(UserProperties(readRev.GetProperties()), UserProperties (body.GetProperties())); // Try to update the first rev, which should fail: bool gotException = false; try { database.PutLocalRevision(rev2input, rev1.GetRevId()); } catch (CouchbaseLiteException e) { NUnit.Framework.Assert.AreEqual(Status.Conflict, e.GetCBLStatus().GetCode()); gotException = true; } NUnit.Framework.Assert.IsTrue(gotException); // Delete it: RevisionInternal revD = new RevisionInternal(rev2.GetDocId(), null, true, database ); gotException = false; try { RevisionInternal revResult = database.PutLocalRevision(revD, null); NUnit.Framework.Assert.IsNull(revResult); } catch (CouchbaseLiteException e) { NUnit.Framework.Assert.AreEqual(Status.Conflict, e.GetCBLStatus().GetCode()); gotException = true; } NUnit.Framework.Assert.IsTrue(gotException); revD = database.PutLocalRevision(revD, rev2.GetRevId()); // Delete nonexistent doc: gotException = false; RevisionInternal revFake = new RevisionInternal("_local/fake", null, true, database ); try { database.PutLocalRevision(revFake, null); } catch (CouchbaseLiteException e) { NUnit.Framework.Assert.AreEqual(Status.NotFound, e.GetCBLStatus().GetCode()); gotException = true; } NUnit.Framework.Assert.IsTrue(gotException); // Read it back (should fail): readRev = database.GetLocalDocument(revD.GetDocId(), null); NUnit.Framework.Assert.IsNull(readRev); }
// Apply the options in the URL query to the specified revision and create a new revision object private static RevisionInternal ApplyOptions(DocumentContentOptions options, RevisionInternal rev, ICouchbaseListenerContext context, Database db, Status outStatus) { if ((options & (DocumentContentOptions.IncludeRevs | DocumentContentOptions.IncludeRevsInfo | DocumentContentOptions.IncludeConflicts | DocumentContentOptions.IncludeAttachments | DocumentContentOptions.IncludeLocalSeq)) != 0) { var dst = rev.GetProperties(); if (options.HasFlag(DocumentContentOptions.IncludeLocalSeq)) { dst["_local_seq"] = rev.GetSequence(); } if (options.HasFlag(DocumentContentOptions.IncludeRevs)) { dst["_revisions"] = db.GetRevisionHistoryDict(rev); } if (options.HasFlag(DocumentContentOptions.IncludeRevsInfo)) { dst["_revs_info"] = db.GetRevisionHistory(rev).Select(x => { string status = "available"; if (x.IsDeleted()) { status = "deleted"; } else if (x.IsMissing()) { status = "missing"; } return(new Dictionary <string, object> { { "rev", x.GetRevId() }, { "status", status } }); }); } if (options.HasFlag(DocumentContentOptions.IncludeConflicts)) { RevisionList revs = db.GetAllRevisionsOfDocumentID(rev.GetDocId(), true); if (revs.Count > 1) { dst["_conflicts"] = revs.Select(x => { return(x.Equals(rev) || x.IsDeleted() ? null : x.GetRevId()); }); } } RevisionInternal nuRev = new RevisionInternal(dst); if (options.HasFlag(DocumentContentOptions.IncludeAttachments)) { bool attEncodingInfo = context.GetQueryParam <bool>("att_encoding_info", bool.TryParse, false); if (!db.ExpandAttachments(nuRev, 0, false, !attEncodingInfo, outStatus)) { return(null); } } rev = nuRev; } return(rev); }
public bool ArePropertiesAvailable() { return(revisionInternal.GetProperties() != null); }
/// <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>Queries the view.</summary> /// <remarks>Queries the view. Does NOT first update the index.</remarks> /// <param name="options">The options to use.</param> /// <returns>An array of QueryRow objects.</returns> /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal IEnumerable <QueryRow> QueryWithOptions(QueryOptions options) { if (options == null) { options = new QueryOptions(); } Cursor cursor = null; IList <QueryRow> rows = new AList <QueryRow>(); try { cursor = ResultSetWithOptions(options); int groupLevel = options.GetGroupLevel(); var group = options.IsGroup() || (groupLevel > 0); var reduce = options.IsReduce() || group; var reduceBlock = Reduce; if (reduce && (reduceBlock == null) && !group) { var msg = "Cannot use reduce option in view " + Name + " which has no reduce block defined"; Log.W(Database.Tag, msg); throw new CouchbaseLiteException(StatusCode.BadRequest); } if (reduce || group) { // Reduced or grouped query: rows = ReducedQuery(cursor, group, groupLevel); } else { // regular query cursor.MoveToNext(); while (!cursor.IsAfterLast()) { var keyData = FromJSON(cursor.GetBlob(0)); // TODO: delay parsing this for increased efficiency var value = FromJSON(cursor.GetBlob(1)); // TODO: ditto var docId = cursor.GetString(2); var sequenceLong = cursor.GetLong(3); var sequence = Convert.ToInt32(sequenceLong); IDictionary <string, object> docContents = null; if (options.IsIncludeDocs()) { // http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views#Linked_documents if (value is IDictionary <string, object> && ((IDictionary <string, object>)value).ContainsKey("_id")) { string linkedDocId = (string)((IDictionary <string, object>)value).Get("_id"); RevisionInternal linkedDoc = Database.GetDocumentWithIDAndRev(linkedDocId, null, EnumSet.NoneOf <TDContentOptions>()); docContents = linkedDoc.GetProperties(); } else { var revId = cursor.GetString(4); docContents = Database.DocumentPropertiesFromJSON(cursor.GetBlob(5), docId, revId, false, sequenceLong, options.GetContentOptions()); } } var row = new QueryRow(docId, sequence, keyData, value, docContents); row.Database = Database; rows.AddItem <QueryRow>(row); // NOTE.ZJG: Change to `yield return row` to convert to a generator. cursor.MoveToNext(); } } } catch (SQLException e) { var errMsg = string.Format("Error querying view: {0}", this); Log.E(Database.Tag, errMsg, e); throw new CouchbaseLiteException(errMsg, e, new Status(StatusCode.DbError)); } finally { if (cursor != null) { cursor.Close(); } } return(rows); }
private bool UploadMultipartRevision(RevisionInternal revision) { MultipartContent multiPart = null; var length = default(double); var revProps = revision.GetProperties(); var attachments = revProps.Get("_attachments").AsDictionary <string, object>(); if (attachments == null) { return(false); } foreach (var attachmentKey in attachments.Keys) { var attachment = attachments.Get(attachmentKey).AsDictionary <string, object>(); if (attachment.ContainsKey("follows")) { if (multiPart == null) { multiPart = new MultipartContent("related"); try { var json = Manager.GetObjectMapper().WriteValueAsString(revProps); var utf8charset = Encoding.UTF8; //multiPart.Add(new StringContent(json, utf8charset, "application/json"), "param1"); var jsonContent = new StringContent(json, utf8charset, "application/json"); //jsonContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"); multiPart.Add(jsonContent); length += json.Length; } catch (Exception e) { throw Misc.CreateExceptionAndLog(Log.To.Sync, e, TAG, "Not able to serialize revision properties into a multipart request content."); } } var blobStore = LocalDatabase.Attachments; var base64Digest = (string)attachment.Get("digest"); var blobKey = new BlobKey(base64Digest); var inputStream = blobStore.BlobStreamForKey(blobKey); if (inputStream == null) { Log.To.Sync.W(TAG, "Unable to find blob file for blobKey: {0} - Skipping upload of multipart revision.", blobKey); multiPart = null; length = 0; } else { string contentType = null; if (attachment.ContainsKey("content_type")) { contentType = (string)attachment.Get("content_type"); } else { if (attachment.ContainsKey("content-type")) { var message = string.Format("Found attachment that uses content-type" + " field name instead of content_type (see couchbase-lite-android" + " issue #80): " + attachment); Log.To.Sync.W(TAG, message); } } var content = new StreamContent(inputStream); content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = attachmentKey }; content.Headers.ContentType = new MediaTypeHeaderValue(contentType ?? "application/octet-stream"); multiPart.Add(content); length += inputStream.Length; } } } if (multiPart == null) { return(false); } var path = string.Format("/{0}?new_edits=false", revision.DocID); // TODO: need to throttle these requests Log.To.Sync.D(TAG, "{0} uploading multipart request. Revision: {1}", this, revision); SafeAddToChangesCount(1); _remoteSession.SendAsyncMultipartRequest(HttpMethod.Put, path, multiPart, (result, e) => { if (e != null) { var httpError = Misc.Flatten(e).FirstOrDefault(ex => ex is HttpResponseException) as HttpResponseException; if (httpError != null) { if (httpError.StatusCode == System.Net.HttpStatusCode.UnsupportedMediaType) { _dontSendMultipart = true; UploadJsonRevision(revision); } } else { LastError = e; RevisionFailed(); } } else { Log.To.Sync.V(TAG, "{0} sent multipart {1}", this, revision); SafeIncrementCompletedChangesCount(); RemovePending(revision); } }); Log.To.Sync.V(TAG, "{0} queuing revision (multipart, {1}kb)", this, length / 1024.0); return(true); }
/// <exception cref="System.Exception"></exception> public virtual void TestAttachments() { string testAttachmentName = "test_attachment"; BlobStore attachments = database.GetAttachments(); NUnit.Framework.Assert.AreEqual(0, attachments.Count()); NUnit.Framework.Assert.AreEqual(new HashSet <object>(), attachments.AllKeys()); Status status = new Status(); IDictionary <string, object> rev1Properties = new Dictionary <string, object>(); rev1Properties.Put("foo", 1); rev1Properties.Put("bar", false); RevisionInternal rev1 = database.PutRevision(new RevisionInternal(rev1Properties, database), null, false, status); NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode()); byte[] attach1 = Sharpen.Runtime.GetBytesForString("This is the body of attach1"); database.InsertAttachmentForSequenceWithNameAndType(new ByteArrayInputStream(attach1 ), rev1.GetSequence(), testAttachmentName, "text/plain", rev1.GetGeneration()); NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode()); //We must set the no_attachments column for the rev to false, as we are using an internal //private API call above (database.insertAttachmentForSequenceWithNameAndType) which does //not set the no_attachments column on revs table try { ContentValues args = new ContentValues(); args.Put("no_attachments=", false); database.GetDatabase().Update("revs", args, "sequence=?", new string[] { rev1.GetSequence ().ToString() }); } catch (SQLException e) { Log.E(Database.Tag, "Error setting rev1 no_attachments to false", e); throw new CouchbaseLiteException(Status.InternalServerError); } Attachment attachment = database.GetAttachmentForSequence(rev1.GetSequence(), testAttachmentName ); NUnit.Framework.Assert.AreEqual("text/plain", attachment.GetContentType()); byte[] data = IOUtils.ToByteArray(attachment.GetContent()); NUnit.Framework.Assert.IsTrue(Arrays.Equals(attach1, data)); IDictionary <string, object> innerDict = new Dictionary <string, object>(); innerDict.Put("content_type", "text/plain"); innerDict.Put("digest", "sha1-gOHUOBmIMoDCrMuGyaLWzf1hQTE="); innerDict.Put("length", 27); innerDict.Put("stub", true); innerDict.Put("revpos", 1); IDictionary <string, object> attachmentDict = new Dictionary <string, object>(); attachmentDict.Put(testAttachmentName, innerDict); IDictionary <string, object> attachmentDictForSequence = database.GetAttachmentsDictForSequenceWithContent (rev1.GetSequence(), EnumSet.NoneOf <Database.TDContentOptions>()); NUnit.Framework.Assert.AreEqual(attachmentDict, attachmentDictForSequence); RevisionInternal gotRev1 = database.GetDocumentWithIDAndRev(rev1.GetDocId(), rev1 .GetRevId(), EnumSet.NoneOf <Database.TDContentOptions>()); IDictionary <string, object> gotAttachmentDict = (IDictionary <string, object>)gotRev1 .GetProperties().Get("_attachments"); NUnit.Framework.Assert.AreEqual(attachmentDict, gotAttachmentDict); // Check the attachment dict, with attachments included: Sharpen.Collections.Remove(innerDict, "stub"); innerDict.Put("data", Base64.EncodeBytes(attach1)); attachmentDictForSequence = database.GetAttachmentsDictForSequenceWithContent(rev1 .GetSequence(), EnumSet.Of(Database.TDContentOptions.TDIncludeAttachments)); NUnit.Framework.Assert.AreEqual(attachmentDict, attachmentDictForSequence); gotRev1 = database.GetDocumentWithIDAndRev(rev1.GetDocId(), rev1.GetRevId(), EnumSet .Of(Database.TDContentOptions.TDIncludeAttachments)); gotAttachmentDict = (IDictionary <string, object>)gotRev1.GetProperties().Get("_attachments" ); NUnit.Framework.Assert.AreEqual(attachmentDict, gotAttachmentDict); // Add a second revision that doesn't update the attachment: IDictionary <string, object> rev2Properties = new Dictionary <string, object>(); rev2Properties.Put("_id", rev1.GetDocId()); rev2Properties.Put("foo", 2); rev2Properties.Put("bazz", false); RevisionInternal rev2 = database.PutRevision(new RevisionInternal(rev2Properties, database), rev1.GetRevId(), false, status); NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode()); database.CopyAttachmentNamedFromSequenceToSequence(testAttachmentName, rev1.GetSequence (), rev2.GetSequence()); // Add a third revision of the same document: IDictionary <string, object> rev3Properties = new Dictionary <string, object>(); rev3Properties.Put("_id", rev2.GetDocId()); rev3Properties.Put("foo", 2); rev3Properties.Put("bazz", false); RevisionInternal rev3 = database.PutRevision(new RevisionInternal(rev3Properties, database), rev2.GetRevId(), false, status); NUnit.Framework.Assert.AreEqual(Status.Created, status.GetCode()); byte[] attach2 = Sharpen.Runtime.GetBytesForString("<html>And this is attach2</html>" ); database.InsertAttachmentForSequenceWithNameAndType(new ByteArrayInputStream(attach2 ), rev3.GetSequence(), testAttachmentName, "text/html", rev2.GetGeneration()); // Check the 2nd revision's attachment: Attachment attachment2 = database.GetAttachmentForSequence(rev2.GetSequence(), testAttachmentName ); NUnit.Framework.Assert.AreEqual("text/plain", attachment2.GetContentType()); data = IOUtils.ToByteArray(attachment2.GetContent()); NUnit.Framework.Assert.IsTrue(Arrays.Equals(attach1, data)); // Check the 3rd revision's attachment: Attachment attachment3 = database.GetAttachmentForSequence(rev3.GetSequence(), testAttachmentName ); NUnit.Framework.Assert.AreEqual("text/html", attachment3.GetContentType()); data = IOUtils.ToByteArray(attachment3.GetContent()); NUnit.Framework.Assert.IsTrue(Arrays.Equals(attach2, data)); IDictionary <string, object> attachmentDictForRev3 = (IDictionary <string, object>) database.GetAttachmentsDictForSequenceWithContent(rev3.GetSequence(), EnumSet.NoneOf <Database.TDContentOptions>()).Get(testAttachmentName); if (attachmentDictForRev3.ContainsKey("follows")) { if (((bool)attachmentDictForRev3.Get("follows")) == true) { throw new RuntimeException("Did not expected attachment dict 'follows' key to be true" ); } else { throw new RuntimeException("Did not expected attachment dict to have 'follows' key" ); } } // Examine the attachment store: NUnit.Framework.Assert.AreEqual(2, attachments.Count()); ICollection <BlobKey> expected = new HashSet <BlobKey>(); expected.AddItem(BlobStore.KeyForBlob(attach1)); expected.AddItem(BlobStore.KeyForBlob(attach2)); NUnit.Framework.Assert.AreEqual(expected, attachments.AllKeys()); database.Compact(); // This clears the body of the first revision NUnit.Framework.Assert.AreEqual(1, attachments.Count()); ICollection <BlobKey> expected2 = new HashSet <BlobKey>(); expected2.AddItem(BlobStore.KeyForBlob(attach2)); NUnit.Framework.Assert.AreEqual(expected2, attachments.AllKeys()); }