public InputStream GetContent()
 {
     if (body != null)
     {
         return(body);
     }
     else
     {
         Database db = revision.GetDatabase();
         Couchbase.Lite.Attachment attachment = db.GetAttachmentForSequence(revision.GetSequence
                                                                                (), this.name);
         body = attachment.GetContent();
         return(body);
     }
 }
 /// <summary>Creates or updates an attachment.</summary>
 /// <remarks>
 /// Creates or updates an attachment.
 /// The attachment data will be written to the database when the revision is saved.
 /// </remarks>
 /// <param name="attachment">A newly-created Attachment (not yet associated with any revision)</param>
 /// <param name="name">The attachment name.</param>
 internal void AddAttachment(Attachment attachment, string name)
 {
     var attachments = Properties.Get("_attachments").AsDictionary<string,object>();
     if (attachments == null)
     {
         attachments = new Dictionary<String, Object>();
     }
     attachments[name] = attachment;
     Properties["_attachments"] = attachments;
     if (attachment != null)
     {
         attachment.Name = name;
         attachment.Revision = this;
     }
 }
        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);
        }
        /// <summary>Creates or updates an attachment.</summary>
        /// <remarks>
        /// Creates or updates an attachment.
        /// The attachment data will be written to the database when the revision is saved.
        /// </remarks>
        /// <param name="attachment">A newly-created Attachment (not yet associated with any revision)</param>
        /// <param name="name">The attachment name.</param>
        internal void AddAttachment(Attachment attachment, string name)
        {
            var attachments = Properties.Get("_attachments").AsDictionary<string, object>() ??
                new Dictionary<string, object>();
            var oldAttach = attachments.GetCast<Attachment>(name);
            if(oldAttach != null) {
                oldAttach.Dispose();
            }

            if(attachment == null) {
                attachments.Remove(name);
            } else {
                attachments[name] = attachment;
            }

            Properties["_attachments"] = attachments;
            if(attachment != null) {
                attachment.Name = name;
            }
        }
        public void TestGetAttachmentBodyUsingPrefetch()
        {
            // add a doc with an attachment
            var doc = database.CreateDocument();
            var rev = doc.CreateRevision();

            var properties = new Dictionary<string, object>();
            properties["foo"] = "bar";
            rev.SetUserProperties(properties);

            var attachBodyBytes = Encoding.UTF8.GetBytes("attach body");
            var attachment = new Attachment(new MemoryStream(attachBodyBytes), "text/plain");
            string attachmentName = "test_attachment.txt";
            rev.AddAttachment(attachment, attachmentName);
            rev.Save();

            // do query that finds that doc with prefetch

            var view = database.GetView("aview");
            view.SetMapReduce((IDictionary<string, object> document, EmitDelegate emitter)=>
                {
                    var id = (string)document["_id"];
                    emitter(id, null);
                }, null, "1");

            // try to get the attachment
            var query = view.CreateQuery();
            query.Prefetch=true;
            var results = query.Run();
            foreach(var row in results)
            {
                // This returns the revision just fine, but the sequence number
                // is set to 0.
                var revision = row.Document.CurrentRevision;
                //var attachments = revision.AttachmentNames.ToList();

                // This returns an Attachment object which looks ok, except again
                // its sequence number is 0. The metadata property knows about
                // the length and mime type of the attachment. It also says
                // "stub" -> "true".
                var attachmentRetrieved = revision.GetAttachment(attachmentName);
                var inputStream = attachmentRetrieved.ContentStream;
                Assert.IsNotNull(inputStream);

                var attachmentDataRetrieved = attachmentRetrieved.Content.ToArray();
                var attachmentDataRetrievedString = Runtime.GetStringForBytes(attachmentDataRetrieved);
                var attachBodyString = Sharpen.Runtime.GetStringForBytes(attachBodyBytes);
                Assert.AreEqual(attachBodyString, attachmentDataRetrievedString);
                // Cleanup
                attachmentRetrieved.Dispose();
            }
            // Cleanup.
            attachment.Dispose();
        }
        internal Attachment GetAttachmentForSequence (long sequence, string filename)
        {
            Debug.Assert((sequence > 0));
            Debug.Assert((filename != null));

            Cursor cursor = null;
            var args = new [] { Convert.ToString(sequence), filename };
            try
            {
                cursor = StorageEngine.RawQuery("SELECT key, type FROM attachments WHERE sequence=? AND filename=?", args);

                if (!cursor.MoveToNext())
                {
                    throw new CouchbaseLiteException(StatusCode.NotFound);
                }

                var keyData = cursor.GetBlob(0);

                //TODO add checks on key here? (ios version)
                var key = new BlobKey(keyData);
                var contentStream = Attachments.BlobStreamForKey(key);
                if (contentStream == null)
                {
                    Log.E(Tag, "Failed to load attachment");
                    throw new CouchbaseLiteException(StatusCode.InternalServerError);
                }
                else
                {
                    var result = new Attachment(contentStream, cursor.GetString(1));
                    result.Compressed = Attachments.IsGZipped(key);
                    return result;
                }
            }
            catch (SQLException)
            {
                throw new CouchbaseLiteException(StatusCode.InternalServerError);
            }
            finally
            {
                if (cursor != null)
                {
                    cursor.Close();
                }
            }
        }
 internal void AddAttachment(Attachment attachment, string name)
 {
     IDictionary<string, object> attachments = (IDictionary<string, object>)properties
         .Get("_attachments");
     if (attachments == null)
     {
         attachments = new Dictionary<string, object>();
     }
     attachments.Put(name, attachment);
     properties.Put("_attachments", attachments);
     if (attachment != null)
     {
         attachment.SetName(name);
         attachment.SetRevision(this);
     }
 }
 public void SetAttachment(string name, string contentType, InputStream contentStream
     )
 {
     Attachment attachment = new Attachment(contentStream, contentType);
     AddAttachment(attachment, name);
 }
 /// <summary>
 /// Sets the attachment with the given name.
 /// </summary>
 /// <remarks>
 /// Sets the <see cref="Couchbase.Lite.Attachment"/> with the given name. 
 /// The <see cref="Couchbase.Lite.Attachment"/> data will be written to 
 /// the <see cref="Couchbase.Lite.Database"/> when the 
 /// <see cref="Couchbase.Lite.Revision"/> is saved.
 /// </remarks>
 /// <param name="name">The name of the <see cref="Couchbase.Lite.Attachment"/> to set.</param>
 /// <param name="contentType">The content-type of the <see cref="Couchbase.Lite.Attachment"/>.</param>
 /// <param name="content">The <see cref="Couchbase.Lite.Attachment"/> content.</param>
 public void SetAttachment(String name, String contentType, Stream content) {
     var attachment = new Attachment(content, contentType);
     AddAttachment(attachment, name);
 }
 /// <summary>
 /// Sets the attachment with the given name.
 /// </summary>
 /// <remarks>
 /// Sets the <see cref="Couchbase.Lite.Attachment"/> with the given name. 
 /// The <see cref="Couchbase.Lite.Attachment"/> data will be written to 
 /// the <see cref="Couchbase.Lite.Database"/> when the 
 /// <see cref="Couchbase.Lite.Revision"/> is saved.
 /// </remarks>
 /// <param name="name">The name of the <see cref="Couchbase.Lite.Attachment"/> to set.</param>
 /// <param name="contentType">The content-type of the <see cref="Couchbase.Lite.Attachment"/>.</param>
 /// <param name="content">The <see cref="Couchbase.Lite.Attachment"/> content.</param>
 public void SetAttachment(String name, String contentType, IEnumerable<Byte> content) {
     var attachment = new Attachment(new MemoryStream(content.ToArray()), contentType);
     AddAttachment(attachment, name);
 }
Exemple #11
0
		public Attachment GetAttachmentForSequence(long sequence, string filename)
		{
			System.Diagnostics.Debug.Assert((sequence > 0));
			System.Diagnostics.Debug.Assert((filename != null));
			Cursor cursor = null;
			string[] args = new string[] { System.Convert.ToString(sequence), filename };
			try
			{
				cursor = database.RawQuery("SELECT key, type FROM attachments WHERE sequence=? AND filename=?"
					, args);
				if (!cursor.MoveToNext())
				{
					throw new CouchbaseLiteException(Status.NotFound);
				}
				byte[] keyData = cursor.GetBlob(0);
				//TODO add checks on key here? (ios version)
				BlobKey key = new BlobKey(keyData);
				InputStream contentStream = attachments.BlobStreamForKey(key);
				if (contentStream == null)
				{
					Log.E(Database.Tag, "Failed to load attachment");
					throw new CouchbaseLiteException(Status.InternalServerError);
				}
				else
				{
					Attachment result = new Attachment(contentStream, cursor.GetString(1));
					result.SetGZipped(attachments.IsGZipped(key));
					return result;
				}
			}
			catch (SQLException)
			{
				throw new CouchbaseLiteException(Status.InternalServerError);
			}
			finally
			{
				if (cursor != null)
				{
					cursor.Close();
				}
			}
		}
		/// <summary>https://github.com/couchbase/couchbase-lite-android/issues/134</summary>
		/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
		/// <exception cref="System.IO.IOException"></exception>
		public virtual void TestGetAttachmentBodyUsingPrefetch()
		{
			// add a doc with an attachment
			Document doc = database.CreateDocument();
			UnsavedRevision rev = doc.CreateRevision();
			IDictionary<string, object> properties = new Dictionary<string, object>();
			properties.Put("foo", "bar");
			rev.SetUserProperties(properties);
			byte[] attachBodyBytes = Sharpen.Runtime.GetBytesForString("attach body");
			Attachment attachment = new Attachment(new ByteArrayInputStream(attachBodyBytes), 
				"text/plain");
			string attachmentName = "test_attachment.txt";
			rev.AddAttachment(attachment, attachmentName);
			rev.Save();
			// do query that finds that doc with prefetch
			View view = database.GetView("aview");
			view.SetMapReduce(new _Mapper_432(), null, "1");
			// try to get the attachment
			Query query = view.CreateQuery();
			query.SetPrefetch(true);
			QueryEnumerator results = query.Run();
			while (results.HasNext())
			{
				QueryRow row = results.Next();
				// This returns the revision just fine, but the sequence number
				// is set to 0.
				SavedRevision revision = row.GetDocument().GetCurrentRevision();
				IList<string> attachments = revision.GetAttachmentNames();
				// This returns an Attachment object which looks ok, except again
				// its sequence number is 0. The metadata property knows about
				// the length and mime type of the attachment. It also says
				// "stub" -> "true".
				Attachment attachmentRetrieved = revision.GetAttachment(attachmentName);
				// This throws a CouchbaseLiteException with Status.NOT_FOUND.
				InputStream @is = attachmentRetrieved.GetContent();
				NUnit.Framework.Assert.IsNotNull(@is);
				byte[] attachmentDataRetrieved = TextUtils.Read(@is);
				string attachmentDataRetrievedString = Sharpen.Runtime.GetStringForBytes(attachmentDataRetrieved
					);
				string attachBodyString = Sharpen.Runtime.GetStringForBytes(attachBodyBytes);
				NUnit.Framework.Assert.AreEqual(attachBodyString, attachmentDataRetrievedString);
			}
		}
        /// <exception cref="System.Exception"></exception>
        public virtual void TestAttachments()
        {
            string    testAttachmentName = "test_attachment";
            BlobStore attachments        = database.Attachments;

            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["foo"] = 1;
            rev1Properties["bar"] = false;
            RevisionInternal rev1 = database.PutRevision(new RevisionInternal(rev1Properties,
                                                                              database), null, false, status);

            NUnit.Framework.Assert.AreEqual(StatusCode.Created, status.GetCode());
            byte[] attach1 = Sharpen.Runtime.GetBytesForString("This is the body of attach1");
            database.InsertAttachmentForSequenceWithNameAndType(new ByteArrayInputStream(attach1
                                                                                         ), rev1.Sequence, testAttachmentName, "text/plain", rev1.GetGeneration());
            NUnit.Framework.Assert.AreEqual(StatusCode.Created, status.GetCode());
            Attachment attachment = database.GetAttachmentForSequence(rev1.Sequence, 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["content_type"] = "text/plain";
            innerDict["digest"]       = "sha1-gOHUOBmIMoDCrMuGyaLWzf1hQTE=";
            innerDict["length"]       = 27;
            innerDict["stub"]         = true;
            innerDict["revpos"]       = 1;
            IDictionary <string, object> attachmentDict = new Dictionary <string, object>();

            attachmentDict[testAttachmentName] = innerDict;
            IDictionary <string, object> attachmentDictForSequence = database.GetAttachmentsDictForSequenceWithContent
                                                                         (rev1.Sequence, EnumSet.NoneOf <TDContentOptions>());

            NUnit.Framework.Assert.AreEqual(attachmentDict, attachmentDictForSequence);
            RevisionInternal gotRev1 = database.GetDocumentWithIDAndRev(rev1.GetDocId(), rev1
                                                                        .GetRevId(), EnumSet.NoneOf <TDContentOptions>());
            IDictionary <string, object> gotAttachmentDict = (IDictionary <string, object>)gotRev1
                                                             .Properties["_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
                                                                                          .Sequence, EnumSet.Of(TDContentOptions.TDIncludeAttachments));
            NUnit.Framework.Assert.AreEqual(attachmentDict, attachmentDictForSequence);
            gotRev1 = database.GetDocumentWithIDAndRev(rev1.GetDocId(), rev1.GetRevId(), EnumSet
                                                       .Of(TDContentOptions.TDIncludeAttachments));
            gotAttachmentDict = (IDictionary <string, object>)gotRev1.Properties.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["foo"]  = 2;
            rev2Properties["bazz"] = false;
            RevisionInternal rev2 = database.PutRevision(new RevisionInternal(rev2Properties,
                                                                              database), rev1.GetRevId(), false, status);

            NUnit.Framework.Assert.AreEqual(StatusCode.Created, status.GetCode());
            database.CopyAttachmentNamedFromSequenceToSequence(testAttachmentName, rev1.GetSequence
                                                                   (), rev2.Sequence);
            // Add a third revision of the same document:
            IDictionary <string, object> rev3Properties = new Dictionary <string, object>();

            rev3Properties.Put("_id", rev2.GetDocId());
            rev3Properties["foo"]  = 2;
            rev3Properties["bazz"] = false;
            RevisionInternal rev3 = database.PutRevision(new RevisionInternal(rev3Properties,
                                                                              database), rev2.GetRevId(), false, status);

            NUnit.Framework.Assert.AreEqual(StatusCode.Created, status.GetCode());
            byte[] attach2 = Sharpen.Runtime.GetBytesForString("<html>And this is attach2</html>"
                                                               );
            database.InsertAttachmentForSequenceWithNameAndType(new ByteArrayInputStream(attach2
                                                                                         ), rev3.Sequence, testAttachmentName, "text/html", rev2.GetGeneration());
            // Check the 2nd revision's attachment:
            Attachment attachment2 = database.GetAttachmentForSequence(rev2.Sequence, 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.Sequence, 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());
            status = database.Compact();
            // This clears the body of the first revision
            NUnit.Framework.Assert.AreEqual(StatusCode.Ok, status.GetCode());
            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());
        }
        /// <exception cref="System.Exception"></exception>
        public virtual void TestPutLargeAttachment()
        {
            string    testAttachmentName = "test_attachment";
            BlobStore attachments        = database.Attachments;

            attachments.DeleteBlobs();
            NUnit.Framework.Assert.AreEqual(0, attachments.Count());
            Status status = new Status();
            IDictionary <string, object> rev1Properties = new Dictionary <string, object>();

            rev1Properties["foo"] = 1;
            rev1Properties["bar"] = false;
            RevisionInternal rev1 = database.PutRevision(new RevisionInternal(rev1Properties,
                                                                              database), null, false, status);

            NUnit.Framework.Assert.AreEqual(StatusCode.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.Sequence, testAttachmentName, "text/plain", rev1.GetGeneration());
            Attachment attachment = database.GetAttachmentForSequence(rev1.Sequence, testAttachmentName
                                                                      );

            NUnit.Framework.Assert.AreEqual("text/plain", attachment.GetContentType());
            byte[] data = IOUtils.ToByteArray(attachment.GetContent());
            NUnit.Framework.Assert.IsTrue(Arrays.Equals(attach1, data));
            EnumSet <TDContentOptions> contentOptions = EnumSet.Of(TDContentOptions
                                                                   .TDIncludeAttachments, TDContentOptions.TDBigAttachmentsFollow);
            IDictionary <string, object> attachmentDictForSequence = database.GetAttachmentsDictForSequenceWithContent
                                                                         (rev1.Sequence, contentOptions);
            IDictionary <string, object> innerDict = (IDictionary <string, object>)attachmentDictForSequence
                                                     [testAttachmentName];

            if (!innerDict.ContainsKey("stub"))
            {
                throw new RuntimeException("Expected attachment dict to have 'stub' key");
            }
            if (((bool)innerDict["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.Properties;
            // rev1PropertiesPrime.put("foo", 2);
            IDictionary <string, object> rev1WithAttachmentsProperties = rev1WithAttachments.GetProperties
                                                                             ();
            IDictionary <string, object> rev2Properties = new Dictionary <string, object>();

            rev2Properties.Put("_id", rev1WithAttachmentsProperties["_id"]);
            rev2Properties["foo"] = 2;
            RevisionInternal newRev = new RevisionInternal(rev2Properties, database);
            RevisionInternal rev2   = database.PutRevision(newRev, rev1WithAttachments.GetRevId
                                                               (), false, status);

            NUnit.Framework.Assert.AreEqual(StatusCode.Created, status.GetCode());
            database.CopyAttachmentNamedFromSequenceToSequence(testAttachmentName, rev1WithAttachments
                                                               .Sequence, rev2.Sequence);
            // 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.Properties["_id"]);
            rev3Properties["foo"] = 3;
            rev3Properties["baz"] = false;
            RevisionInternal rev3 = new RevisionInternal(rev3Properties, database);

            rev3 = database.PutRevision(rev3, rev2.GetRevId(), false, status);
            NUnit.Framework.Assert.AreEqual(StatusCode.Created, status.GetCode());
            byte[] attach3 = Sharpen.Runtime.GetBytesForString("<html><blink>attach3</blink></html>"
                                                               );
            database.InsertAttachmentForSequenceWithNameAndType(new ByteArrayInputStream(attach3
                                                                                         ), rev3.Sequence, 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.Attachments.AllKeys();

            NUnit.Framework.Assert.AreEqual(2, blobKeys.Count);
            database.Compact();
            blobKeys = database.Attachments.AllKeys();
            NUnit.Framework.Assert.AreEqual(1, blobKeys.Count);
        }
        /// <summary>
        /// Sets the attachment with the given name.
        /// </summary>
        /// <remarks>
        /// Sets the <see cref="Couchbase.Lite.Attachment"/> with the given name. 
        /// The <see cref="Couchbase.Lite.Attachment"/> data will be written to 
        /// the <see cref="Couchbase.Lite.Database"/> when the 
        /// <see cref="Couchbase.Lite.Revision"/> is saved.
        /// </remarks>
        /// <param name="name">The name of the <see cref="Couchbase.Lite.Attachment"/> to set.</param>
        /// <param name="contentType">The content-type of the <see cref="Couchbase.Lite.Attachment"/>.</param>
        /// <param name="content">The <see cref="Couchbase.Lite.Attachment"/> content.</param>
        public void SetAttachment(string name, string contentType, IEnumerable<byte> content)
        {
            var data = content?.ToArray();
            if(data == null) {
                AddAttachment(null, name);
                return;
            }

            var stream = RecyclableMemoryStreamManager.SharedInstance.GetStream("UnsavedRevision", 
                             data, 0, data.Length);
            var attachment = new Attachment(stream, contentType);
            AddAttachment(attachment, name);

        }