internal static BlobStoreWriter BlobStoreWriterForBody(InputStream body, Database
                                                               database)
        {
            BlobStoreWriter writer = database.GetAttachmentWriter();

            writer.Read(body);
            writer.Finish();
            return(writer);
        }
        /// <exception cref="System.Exception"></exception>
        public virtual void TestBlobStoreWriterForBody()
        {
            BlobStore       attachments      = database.GetAttachments();
            InputStream     attachmentStream = GetAsset("attachment.png");
            BlobStoreWriter blobStoreWriter  = Attachment.BlobStoreWriterForBody(attachmentStream
                                                                                 , database);
            string sha1DigestKey = blobStoreWriter.SHA1DigestString();

            NUnit.Framework.Assert.IsTrue(sha1DigestKey.Contains("LmsoqJJ6LOn4YS60pYnvrKbBd64="
                                                                 ));
        }
		/// <exception cref="System.Exception"></exception>
		public virtual void TestBasicOperation()
		{
			BlobStore attachments = database.GetAttachments();
			InputStream attachmentStream = GetAsset("attachment.png");
			byte[] bytes = IOUtils.ToByteArray(attachmentStream);
			BlobStoreWriter blobStoreWriter = new BlobStoreWriter(attachments);
			blobStoreWriter.AppendData(bytes);
			blobStoreWriter.Finish();
			blobStoreWriter.Install();
			string sha1DigestKey = blobStoreWriter.SHA1DigestString();
			BlobKey keyFromSha1 = new BlobKey(sha1DigestKey);
			NUnit.Framework.Assert.IsTrue(attachments.GetSizeOfBlob(keyFromSha1) == bytes.Length
				);
		}
Exemple #4
0
        public void TestBlobStoreWriter()
        {
            var writer = new BlobStoreWriter(_store);

            writer.AppendData(Encoding.UTF8.GetBytes("part 1, "));
            writer.AppendData(Encoding.UTF8.GetBytes("part 2, "));
            writer.AppendData(Encoding.UTF8.GetBytes("part 3"));
            writer.Finish();
            writer.Install();

            var expectedData = Encoding.UTF8.GetBytes("part 1, part 2, part 3");
            var readItem     = _store.BlobForKey(writer.GetBlobKey());

            readItem.Should().Equal(expectedData, "because the writer should correctly write contents to disk");
            Verify(writer.GetBlobKey(), expectedData);
        }
Exemple #5
0
        /// <exception cref="System.Exception"></exception>
        public virtual void TestBasicOperation()
        {
            BlobStore   attachments      = database.GetAttachments();
            InputStream attachmentStream = GetAsset("attachment.png");

            byte[]          bytes           = IOUtils.ToByteArray(attachmentStream);
            BlobStoreWriter blobStoreWriter = new BlobStoreWriter(attachments);

            blobStoreWriter.AppendData(bytes);
            blobStoreWriter.Finish();
            blobStoreWriter.Install();
            string  sha1DigestKey = blobStoreWriter.SHA1DigestString();
            BlobKey keyFromSha1   = new BlobKey(sha1DigestKey);

            NUnit.Framework.Assert.IsTrue(attachments.GetSizeOfBlob(keyFromSha1) == bytes.Length
                                          );
        }
        public void TestStreamAttachmentBlobStoreWriter()
        {
            var attachments = database.Attachments;
            var blobWriter = new BlobStoreWriter(attachments);
            var testBlob = "foo";
            blobWriter.AppendData(Runtime.GetBytesForString(testBlob));
            blobWriter.Finish();

            var sha1Base64Digest = "sha1-C+7Hteo/D9vJXQ3UfzxbwnXaijM=";
            Assert.AreEqual(blobWriter.SHA1DigestString(), sha1Base64Digest);
            Assert.AreEqual(blobWriter.MD5DigestString(), "md5-rL0Y20zC+Fzt72VPzMSk2A==");

            // install it
            blobWriter.Install();
            // look it up in blob store and make sure it's there
            var blobKey = new BlobKey(sha1Base64Digest);
            var blob = attachments.BlobForKey(blobKey);
            Assert.IsTrue(Arrays.Equals(Runtime.GetBytesForString(testBlob).ToArray(), blob));
        }
        public void TestBasicOperation()
        {
            var attachmentStream = (InputStream)GetAsset("attachment.png");
            var memoryStream = new MemoryStream();
            attachmentStream.Wrapped.CopyTo(memoryStream);
            var bytes = memoryStream.ToArray();

            var attachments = database.Attachments;
            var blobStoreWriter = new BlobStoreWriter(attachments);
            blobStoreWriter.AppendData(bytes);
            blobStoreWriter.Finish();
            blobStoreWriter.Install();

            var sha1DigestKey = blobStoreWriter.SHA1DigestString();
            Assert.IsTrue(sha1DigestKey.Contains("LmsoqJJ6LOn4YS60pYnvrKbBd64="));

            var keyFromSha1 = new BlobKey(sha1DigestKey);
            Assert.IsTrue(attachments.GetSizeOfBlob(keyFromSha1) == bytes.Length);
        }
        internal static IDictionary <string, object> InstallAttachmentBodies(IDictionary <string
                                                                                          , object> attachments, Database database)
        {
            IDictionary <string, object> updatedAttachments = new Dictionary <string, object>();

            foreach (string name in attachments.Keys)
            {
                object value = attachments.Get(name);
                if (value is Couchbase.Lite.Attachment)
                {
                    Couchbase.Lite.Attachment    attachment      = (Couchbase.Lite.Attachment)value;
                    IDictionary <string, object> metadataMutable = new Dictionary <string, object>();
                    metadataMutable.PutAll(attachment.GetMetadata());
                    InputStream body = attachment.GetBodyIfNew();
                    if (body != null)
                    {
                        // Copy attachment body into the database's blob store:
                        BlobStoreWriter writer = BlobStoreWriterForBody(body, database);
                        metadataMutable.Put("length", (long)writer.GetLength());
                        metadataMutable.Put("digest", writer.MD5DigestString());
                        metadataMutable.Put("follows", true);
                        database.RememberAttachmentWriter(writer);
                    }
                    updatedAttachments.Put(name, metadataMutable);
                }
                else
                {
                    if (value is AttachmentInternal)
                    {
                        throw new ArgumentException("AttachmentInternal objects not expected here.  Could indicate a bug"
                                                    );
                    }
                    else
                    {
                        if (value != null)
                        {
                            updatedAttachments.Put(name, value);
                        }
                    }
                }
            }
            return(updatedAttachments);
        }
Exemple #9
0
        public void TestStreamAttachmentBlobStoreWriter()
        {
            var attachments = database.Attachments;
            var blobWriter  = new BlobStoreWriter(attachments);
            var testBlob    = "foo";

            blobWriter.AppendData(Encoding.UTF8.GetBytes(testBlob));
            blobWriter.Finish();

            var sha1Base64Digest = "sha1-C+7Hteo/D9vJXQ3UfzxbwnXaijM=";

            Assert.AreEqual(blobWriter.SHA1DigestString(), sha1Base64Digest);

            // install it
            blobWriter.Install();
            // look it up in blob store and make sure it's there
            var blobKey = new BlobKey(sha1Base64Digest);
            var blob    = attachments.BlobForKey(blobKey);

            CollectionAssert.AreEqual(Encoding.UTF8.GetBytes(testBlob).ToArray(), blob);
        }
Exemple #10
0
        public virtual void TestStreamAttachmentBlobStoreWriter()
        {
            BlobStore       attachments = database.GetAttachments();
            BlobStoreWriter blobWriter  = new BlobStoreWriter(attachments);
            string          testBlob    = "foo";

            blobWriter.AppendData(Sharpen.Runtime.GetBytesForString(new string(testBlob)));
            blobWriter.Finish();
            string sha1Base64Digest = "sha1-C+7Hteo/D9vJXQ3UfzxbwnXaijM=";

            NUnit.Framework.Assert.AreEqual(blobWriter.SHA1DigestString(), sha1Base64Digest);
            NUnit.Framework.Assert.AreEqual(blobWriter.MD5DigestString(), "md5-rL0Y20zC+Fzt72VPzMSk2A=="
                                            );
            // install it
            blobWriter.Install();
            // look it up in blob store and make sure it's there
            BlobKey blobKey = new BlobKey(sha1Base64Digest);

            byte[] blob = attachments.BlobForKey(blobKey);
            NUnit.Framework.Assert.IsTrue(Arrays.Equals(Sharpen.Runtime.GetBytesForString(testBlob
                                                                                          , Sharpen.Extensions.GetEncoding("UTF-8")), blob));
        }
        public void TestBasicOperation()
        {
            var attachmentStream = GetAsset("attachment.png");
            var memoryStream     = new MemoryStream();

            attachmentStream.CopyTo(memoryStream);
            var bytes = memoryStream.ToArray();

            var attachments     = database.Attachments;
            var blobStoreWriter = new BlobStoreWriter(attachments);

            blobStoreWriter.AppendData(bytes);
            blobStoreWriter.Finish();
            blobStoreWriter.Install();

            var sha1DigestKey = blobStoreWriter.SHA1DigestString();

            Assert.IsTrue(sha1DigestKey.Contains("LmsoqJJ6LOn4YS60pYnvrKbBd64="));

            var keyFromSha1 = new BlobKey(sha1DigestKey);

            Assert.IsTrue(attachments.GetSizeOfBlob(keyFromSha1) == bytes.Length);
        }
 internal void RememberAttachmentWriter (BlobStoreWriter writer)
 {
     var digest = writer.SHA1DigestString();
     PendingAttachmentsByDigest[digest] = writer;
 }
        /// <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);
            }
        }
 internal void RememberAttachmentWriter(BlobStoreWriter writer, string digest)
 {
     PendingAttachmentsByDigest[digest] = writer;
 }
 public void FinishedPart()
 {
     if (jsonBuffer != null)
     {
         ParseJsonBuffer();
     }
     else
     {
         curAttachment.Finish();
         String sha1String = curAttachment.SHA1DigestString();
         attachmentsBySHA1Digest.Put(sha1String, curAttachment);
         curAttachment = null;
     }
 }
        public void StartedPart(IDictionary<String, String> headers)
        {
            if (document == null) {
                StartJsonBuffer(headers);
            } else {
                Log.V(TAG, "    Starting attachment #{0}...", attachmentsBySHA1Digest.Count + 1);
                curAttachment = database.AttachmentWriter;
                if (curAttachment == null) {
                    const string msg = "Cannot create blob store writer for the attachment";
                    Log.W(TAG, msg);
                    throw new CouchbaseLiteException(msg, StatusCode.AttachmentError);
                }

                var name = default(string);
                var contentDisposition = headers.Get("Content-Disposition");
                if (contentDisposition != null && contentDisposition.StartsWith("attachment; filename=")) {
                    // TODO: Parse this less simplistically. Right now it assumes it's in exactly the same
                    // format generated by -[CBL_Pusher uploadMultipartRevision:]. CouchDB (as of 1.2) doesn't
                    // output any headers at all on attachments so there's no compatibility issue yet.
                    var contentDispositionUnquoted = Misc.UnquoteString(contentDisposition);
                    name = contentDispositionUnquoted.Substring(21);
                    if (name != null) {
                        attachmentsByName.Put(name, curAttachment);
                    }
                }

                var contentEncoding = headers.Get("Content-Encoding");
                if (contentEncoding == "gzip") {
                    if (name != null) {
                        try {
                            var attachEncoding = document.GetCast<IDictionary<string, object>>("_attachments").
                            GetCast <IDictionary<string, object>>(name).GetCast<string>("encoding");
                            if (attachEncoding != "gzip") {
                                Log.W(TAG, "Attachment '{0}' MIME body is gzipped but attachment isn't", name);
                                throw new CouchbaseLiteException(StatusCode.UnsupportedType);
                            }
                        } catch (NullReferenceException) {
                            throw new CouchbaseLiteException(StatusCode.UnsupportedType);
                        }
                    }
                } else if (contentEncoding != null) {
                    Log.W(TAG, "Received unsupported Content-Encoding '{0}'", contentEncoding);
                    throw new CouchbaseLiteException(StatusCode.UnsupportedType);
                }
            }
        }
        public AtomicAction ActionToChangeEncryptionKey(SymmetricKey newKey)
        {
            var action = new AtomicAction();

            // Find all the blob files:
            var blobs = default(string[]);
            var oldKey = EncryptionKey;
            blobs = Directory.GetFiles(_path, "*" + FileExtension);
            if (blobs.Length == 0) {
                // No blobs, so nothing to encrypt. Just add/remove the encryption marker file:
                action.AddLogic(() =>
                {
                    Log.To.NoDomain.D(TAG, "{0} {1}", (newKey != null) ? "encrypting" : "decrypting", _path);
                    Log.To.NoDomain.D(TAG, "    No blobs to copy; done.");
                    EncryptionKey = newKey;
                    MarkEncrypted(newKey != null);
                }, () =>
                {
                    EncryptionKey = oldKey;
                    MarkEncrypted(oldKey != null);
                }, null);
                return action;
            }

            // Create a new directory for the new blob store. Have to do this now, before starting the
            // action, because farther down we create an action to move it...
            var tempPath = Path.Combine(Path.GetTempPath(), String.Format("CouchbaseLite-Temp-{0}", Misc.CreateGUID()));
            action.AddLogic(() => 
            {
                Log.To.NoDomain.D(TAG, "{0} {1}", (newKey != null) ? "encrypting" : "decrypting", _path);
                Directory.CreateDirectory(tempPath);
            }, () => Directory.Delete(tempPath, true), null);

            var tempStore = default(BlobStore);
            action.AddLogic(() =>
            {
                tempStore = new BlobStore(tempPath, newKey);
                tempStore.MarkEncrypted(true);
            }, null, null);

            // Copy each of my blobs into the new store (which will update its encryption):
            action.AddLogic(() =>
            {
                foreach(var blobName in blobs) {
                    // Copy file by reading with old key and writing with new one:
                    Log.To.NoDomain.D(TAG, "    Copying {0}", blobName);
                    Stream readStream = File.Open(blobName, FileMode.Open, FileAccess.Read, FileShare.Read);
                    if(EncryptionKey != null) {
                        readStream = EncryptionKey.DecryptStream(readStream);
                    }

                    var writer = new BlobStoreWriter(tempStore);
                    try {
                        writer.Read(readStream);
                        writer.Finish();
                        writer.Install();
                    } catch(Exception) {
                        writer.Cancel();
                        throw;
                    } finally {
                        readStream.Dispose();
                    }
                }
            }, null, null);

            // Replace the attachment dir with the new one:
            action.AddLogic(AtomicAction.MoveDirectory(tempPath, _path));

            // Finally update EncryptionKey:
            action.AddLogic(() =>
            {
                EncryptionKey = newKey;
            }, () =>
            {
                EncryptionKey = oldKey;
            }, null);

            return action;
        }
        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();
        }
        /// <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;
        }
 /// <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;
     BlobStoreWriter blobWriter = new BlobStoreWriter(database.GetAttachments());
     blobWriter.AppendData(attachv2);
     blobWriter.Finish();
     try
     {
         database.UpdateAttachment(testAttachmentName, blobWriter, "application/foo", AttachmentInternal.AttachmentEncoding
             .AttachmentEncodingNone, rev1.GetDocId(), null);
     }
     catch (CouchbaseLiteException e)
     {
         gotExpectedErrorCode = (e.GetCBLStatus().GetCode() == Status.Conflict);
     }
     NUnit.Framework.Assert.IsTrue(gotExpectedErrorCode);
     gotExpectedErrorCode = false;
     try
     {
         database.UpdateAttachment(testAttachmentName, blobWriter, "application/foo", AttachmentInternal.AttachmentEncoding
             .AttachmentEncodingNone, 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, blobWriter, "application/foo"
             , AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, 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, AttachmentInternal.AttachmentEncoding
             .AttachmentEncodingNone, 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, AttachmentInternal.AttachmentEncoding
             .AttachmentEncodingNone, "nosuchdoc", "nosuchrev");
     }
     catch (CouchbaseLiteException e)
     {
         gotExpectedErrorCode = (e.GetCBLStatus().GetCode() == Status.NotFound);
     }
     NUnit.Framework.Assert.IsTrue(gotExpectedErrorCode);
     RevisionInternal rev3 = database.UpdateAttachment(testAttachmentName, null, null, 
         AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, 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();
 }
 public void StartedPart(IDictionary<String, String> headers)
 {
     if (document == null)
     {
         jsonBuffer = new List<Byte>(1024);
     }
     else
     {
         curAttachment = database.GetAttachmentWriter();
         var contentDisposition = headers.Get("Content-Disposition");
         if (contentDisposition != null && contentDisposition.StartsWith("attachment; filename="))
         {
             // TODO: Parse this less simplistically. Right now it assumes it's in exactly the same
             // format generated by -[CBL_Pusher uploadMultipartRevision:]. CouchDB (as of 1.2) doesn't
             // output any headers at all on attachments so there's no compatibility issue yet.
             var contentDispositionUnquoted = Misc.UnquoteString(contentDisposition);
             var name = contentDispositionUnquoted.Substring(21);
             if (name != null)
             {
                 attachmentsByName.Put(name, curAttachment);
             }
         }
     }
 }
		public virtual void TestStreamAttachmentBlobStoreWriter()
		{
			BlobStore attachments = database.GetAttachments();
			BlobStoreWriter blobWriter = new BlobStoreWriter(attachments);
			string testBlob = "foo";
			blobWriter.AppendData(Sharpen.Runtime.GetBytesForString(new string(testBlob)));
			blobWriter.Finish();
			string sha1Base64Digest = "sha1-C+7Hteo/D9vJXQ3UfzxbwnXaijM=";
			NUnit.Framework.Assert.AreEqual(blobWriter.SHA1DigestString(), sha1Base64Digest);
			NUnit.Framework.Assert.AreEqual(blobWriter.MD5DigestString(), "md5-rL0Y20zC+Fzt72VPzMSk2A=="
				);
			// install it
			blobWriter.Install();
			// look it up in blob store and make sure it's there
			BlobKey blobKey = new BlobKey(sha1Base64Digest);
			byte[] blob = attachments.BlobForKey(blobKey);
			NUnit.Framework.Assert.IsTrue(Arrays.Equals(Sharpen.Runtime.GetBytesForString(testBlob
				, Sharpen.Extensions.GetEncoding("UTF-8")), blob));
		}
        /// <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;
        }
Exemple #24
0
        public AtomicAction ActionToChangeEncryptionKey(SymmetricKey newKey)
        {
            var action = new AtomicAction();

            // Find all the blob files:
            var blobs  = default(string[]);
            var oldKey = EncryptionKey;

            blobs = Directory.GetFiles(_path, "*" + FileExtension);
            if (blobs.Length == 0)
            {
                // No blobs, so nothing to encrypt. Just add/remove the encryption marker file:
                action.AddLogic(() =>
                {
                    Log.D(TAG, "{0} {1}", (newKey != null) ? "encrypting" : "decrypting", _path);
                    Log.D(TAG, "    No blobs to copy; done.");
                    EncryptionKey = newKey;
                    MarkEncrypted(newKey != null);
                }, () =>
                {
                    EncryptionKey = oldKey;
                    MarkEncrypted(oldKey != null);
                }, null);
                return(action);
            }

            // Create a new directory for the new blob store. Have to do this now, before starting the
            // action, because farther down we create an action to move it...
            var tempPath = Path.Combine(Path.GetTempPath(), String.Format("CouchbaseLite-Temp-{0}", Misc.CreateGUID()));

            action.AddLogic(() =>
            {
                Log.D(TAG, "{0} {1}", (newKey != null) ? "encrypting" : "decrypting", _path);
                Directory.CreateDirectory(tempPath);
            }, () => Directory.Delete(tempPath, true), null);

            var tempStore = default(BlobStore);

            action.AddLogic(() =>
            {
                tempStore = new BlobStore(tempPath, newKey);
                tempStore.MarkEncrypted(true);
            }, null, null);

            // Copy each of my blobs into the new store (which will update its encryption):
            action.AddLogic(() =>
            {
                foreach (var blobName in blobs)
                {
                    // Copy file by reading with old key and writing with new one:
                    Log.D(TAG, "    Copying {0}", blobName);
                    Stream readStream = File.Open(blobName, FileMode.Open, FileAccess.Read, FileShare.Read);
                    if (EncryptionKey != null)
                    {
                        readStream = EncryptionKey.DecryptStream(readStream);
                    }

                    var writer = new BlobStoreWriter(tempStore);
                    try {
                        writer.Read(readStream);
                        writer.Finish();
                        writer.Install();
                    } catch (Exception) {
                        writer.Cancel();
                        throw;
                    } finally {
                        readStream.Dispose();
                    }
                }
            }, null, null);

            // Replace the attachment dir with the new one:
            action.AddLogic(AtomicAction.MoveDirectory(tempPath, _path));

            // Finally update EncryptionKey:
            action.AddLogic(() =>
            {
                EncryptionKey = newKey;
            }, () =>
            {
                EncryptionKey = oldKey;
            }, null);

            return(action);
        }
Exemple #25
0
		internal void RememberAttachmentWriter(BlobStoreWriter writer)
		{
			GetPendingAttachmentsByDigest().Put(writer.MD5DigestString(), writer);
		}
        public void TestStreamAttachmentBlobStoreWriter()
        {
            var attachments = database.Attachments;
            var blobWriter = new BlobStoreWriter(attachments);
            var testBlob = "foo";
            blobWriter.AppendData(Encoding.UTF8.GetBytes(testBlob));
            blobWriter.Finish();

            var sha1Base64Digest = "sha1-C+7Hteo/D9vJXQ3UfzxbwnXaijM=";
            Assert.AreEqual(blobWriter.SHA1DigestString(), sha1Base64Digest);

            // install it
            blobWriter.Install();
            // look it up in blob store and make sure it's there
            var blobKey = new BlobKey(sha1Base64Digest);
            var blob = attachments.BlobForKey(blobKey);
            Assert.IsTrue(Arrays.Equals(Encoding.UTF8.GetBytes(testBlob).ToArray(), blob));
        }
Exemple #27
0
        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.GetDocumentWithIDAndRev(rev1.GetDocId(),
                                                           rev1.GetRevId(), DocumentContentOptions.None);
            var gotAttachmentDict = gotRev1.GetPropertyForKey("_attachments").AsDictionary <string, object>();

            var innerDict = new JObject();

            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.GetCBLStatus().GetCode() == StatusCode.Conflict);
            }
            Assert.IsTrue(gotExpectedErrorCode);
            gotExpectedErrorCode = false;

            try
            {
                database.UpdateAttachment(testAttachmentName, new BlobStoreWriter(database.Attachments), "application/foo",
                                          AttachmentEncoding.None, rev1.GetDocId(), "1-bogus");
            }
            catch (CouchbaseLiteException e)
            {
                gotExpectedErrorCode = (e.GetCBLStatus().GetCode() == 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.GetDocumentWithIDAndRev(rev2.GetDocId(), rev2
                                                                        .GetRevId(), DocumentContentOptions.None);

            attachmentDict            = gotRev2.GetProperties().Get("_attachments").AsDictionary <string, object>();
            innerDict                 = new JObject();
            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.GetCBLStatus().GetCode() == StatusCode.NotFound);
            }
            Assert.IsTrue(gotExpectedErrorCode);
            gotExpectedErrorCode = false;
            try
            {
                database.UpdateAttachment("nosuchattach", null, null,
                                          AttachmentEncoding.None, "nosuchdoc", "nosuchrev");
            }
            catch (CouchbaseLiteException e)
            {
                gotExpectedErrorCode = (e.GetCBLStatus().GetCode() == 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.GetDocumentWithIDAndRev(rev3.GetDocId(), rev3
                                                                        .GetRevId(), DocumentContentOptions.None);

            attachmentDict = gotRev3.GetProperties().Get("_attachments").AsDictionary <string, object>();
            Assert.IsNull(attachmentDict);
            database.Close();
        }
 public void FinishedPart()
 {
     if (jsonBuffer != null)
     {
         ParseJsonBuffer();
     }
     else
     {
         curAttachment.Finish();
         String md5String = curAttachment.MD5DigestString();
         attachmentsByMd5Digest.Put(md5String, curAttachment);
         curAttachment = null;
     }
 }
        /// <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;
            BlobStoreWriter blobWriter           = new BlobStoreWriter(database.GetAttachments());

            blobWriter.AppendData(attachv2);
            blobWriter.Finish();
            try
            {
                database.UpdateAttachment(testAttachmentName, blobWriter, "application/foo", AttachmentInternal.AttachmentEncoding
                                          .AttachmentEncodingNone, rev1.GetDocId(), null);
            }
            catch (CouchbaseLiteException e)
            {
                gotExpectedErrorCode = (e.GetCBLStatus().GetCode() == Status.Conflict);
            }
            NUnit.Framework.Assert.IsTrue(gotExpectedErrorCode);
            gotExpectedErrorCode = false;
            try
            {
                database.UpdateAttachment(testAttachmentName, blobWriter, "application/foo", AttachmentInternal.AttachmentEncoding
                                          .AttachmentEncodingNone, 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, blobWriter, "application/foo"
                                                 , AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, 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, AttachmentInternal.AttachmentEncoding
                                          .AttachmentEncodingNone, 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, AttachmentInternal.AttachmentEncoding
                                          .AttachmentEncodingNone, "nosuchdoc", "nosuchrev");
            }
            catch (CouchbaseLiteException e)
            {
                gotExpectedErrorCode = (e.GetCBLStatus().GetCode() == Status.NotFound);
            }
            NUnit.Framework.Assert.IsTrue(gotExpectedErrorCode);
            RevisionInternal rev3 = database.UpdateAttachment(testAttachmentName, null, null,
                                                              AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, 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();
        }