internal RevisionInternal TransformRevision(RevisionInternal rev)
        {
            if (RevisionBodyTransformationFunction != null) {
                try {
                    var generation = rev.GetGeneration();
                    var xformed = RevisionBodyTransformationFunction(rev);
                    if (xformed == null) {
                        return null;
                    }

                    if (xformed != rev) {
                        Debug.Assert((xformed.GetDocId().Equals(rev.GetDocId())));
                        Debug.Assert((xformed.GetRevId().Equals(rev.GetRevId())));
                        Debug.Assert((xformed.GetProperties().Get("_revisions").Equals(rev.GetProperties().Get("_revisions"))));

                        if (xformed.GetProperties().ContainsKey("_attachments")) {
                            // Insert 'revpos' properties into any attachments added by the callback:
                            var mx = new RevisionInternal(xformed.GetProperties());
                            xformed = mx;
                            mx.MutateAttachments((name, info) =>
                            {
                                if (info.Get("revpos") != null) {
                                    return info;
                                }

                                if (info.Get("data") == null) {
                                    throw new InvalidOperationException("Transformer added attachment without adding data");
                                }

                                var newInfo = new Dictionary<string, object>(info);
                                newInfo["revpos"] = generation;
                                return newInfo;
                            });
                        }
                    }
                } catch (Exception e) {
                    Log.W(TAG, String.Format("Exception transforming a revision of doc '{0}'", rev.GetDocId()), e);
                }
            }

            return rev;
        }
        public IList<String> GetPossibleAncestorRevisionIDs(RevisionInternal rev, int limit, ref Boolean hasAttachment)
        {
            var matchingRevs = new List<String>();
            var generation = rev.GetGeneration();
            if (generation <= 1)
            {
                return null;
            }

            var docNumericID = GetDocNumericID(rev.GetDocId());
            if (docNumericID <= 0)
            {
                return null;
            }

            var sqlLimit = limit > 0 ? limit : -1;

            // SQL uses -1, not 0, to denote 'no limit'
            var sql = @"SELECT revid, sequence FROM revs WHERE doc_id=? and revid < ? and deleted=0 and json not null"
                  + " ORDER BY sequence DESC LIMIT ?";
            var args = new [] { Convert.ToString(docNumericID), generation + "-", sqlLimit.ToString() };
            Cursor cursor = null;
            try
            {
                cursor = StorageEngine.RawQuery(sql, args);
                cursor.MoveToNext();

                if (!cursor.IsAfterLast())
                {
                    if (matchingRevs.Count == 0)
                    {
                        hasAttachment = SequenceHasAttachments(cursor.GetLong(1));
                    }
                    matchingRevs.AddItem(cursor.GetString(0));
                }
            }
            catch (SQLException e)
            {
                Log.E(Database.Tag, "Error getting all revisions of document", e);
            }
            finally
            {
                if (cursor != null)
                {
                    cursor.Close();
                }
            }
            return matchingRevs;
        }
        /// <summary>
        /// Given a newly-added revision, adds the necessary attachment rows to the sqliteDb and
        /// stores inline attachments into the blob store.
        /// </summary>
        /// <remarks>
        /// Given a newly-added revision, adds the necessary attachment rows to the sqliteDb and
        /// stores inline attachments into the blob store.
        /// </remarks>
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
        internal void ProcessAttachmentsForRevision(IDictionary<string, AttachmentInternal> attachments, RevisionInternal rev, long parentSequence)
        {
            Debug.Assert((rev != null));
            var newSequence = rev.GetSequence();
            Debug.Assert((newSequence > parentSequence));
            var generation = rev.GetGeneration();
            Debug.Assert((generation > 0));

            // If there are no attachments in the new rev, there's nothing to do:
            IDictionary<string, object> revAttachments = null;
            var properties = rev.GetProperties ();
            if (properties != null) {
                revAttachments = properties.Get("_attachments").AsDictionary<string, object>();
            }

            if (revAttachments == null || revAttachments.Count == 0 || rev.IsDeleted()) {
                return;
            }

            foreach (string name in revAttachments.Keys)
            {
                var attachment = attachments.Get(name);
                if (attachment != null)
                {
                    // Determine the revpos, i.e. generation # this was added in. Usually this is
                    // implicit, but a rev being pulled in replication will have it set already.
                    if (attachment.RevPos == 0)
                    {
                        attachment.RevPos = generation;
                    }
                    else
                    {
                        if (attachment.RevPos > generation)
                        {
                            Log.W(TAG, string.Format("Attachment {0} {1} has unexpected revpos {2}, setting to {3}", rev, name, attachment.RevPos, generation));
                            attachment.RevPos = generation;
                        }
                    }
                }
            }
        }
        internal void StubOutAttachmentsInRevision(IDictionary<String, AttachmentInternal> attachments, RevisionInternal rev)
        {
            var properties = rev.GetProperties();
            var attachmentProps = properties.Get("_attachments");
            if (attachmentProps != null)
            {
                var nuAttachments = new Dictionary<string, object>();
                foreach (var kvp in attachmentProps.AsDictionary<string,object>())
                {
                    var attachmentValue = kvp.Value.AsDictionary<string,object>();
                    if (attachmentValue.ContainsKey("follows") || attachmentValue.ContainsKey("data"))
                    {
                        attachmentValue.Remove("follows");
                        attachmentValue.Remove("data");

                        attachmentValue["stub"] = true;
                        if (attachmentValue.Get("revpos") == null)
                        {
                            attachmentValue.Put("revpos", rev.GetGeneration());
                        }

                        var attachmentObject = attachments.Get(kvp.Key);
                        if (attachmentObject != null)
                        {
                            attachmentValue.Put("length", attachmentObject.GetLength());
                            if (attachmentObject.GetBlobKey() != null)
                            {
                                attachmentValue.Put("digest", attachmentObject.GetBlobKey().Base64Digest());
                            }
                        }
                    }
                    nuAttachments[kvp.Key] = attachmentValue;
                }

                properties["_attachments"] = nuAttachments;  
            }
        }
        /// <summary>
        /// Given a newly-added revision, adds the necessary attachment rows to the sqliteDb and
        /// stores inline attachments into the blob store.
        /// </summary>
        /// <remarks>
        /// Given a newly-added revision, adds the necessary attachment rows to the sqliteDb and
        /// stores inline attachments into the blob store.
        /// </remarks>
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
        internal void ProcessAttachmentsForRevision(IDictionary<string, AttachmentInternal> attachments, RevisionInternal rev, long parentSequence)
        {
            Debug.Assert((rev != null));
            var newSequence = rev.GetSequence();
            Debug.Assert((newSequence > parentSequence));
            var generation = rev.GetGeneration();
            Debug.Assert((generation > 0));

            // If there are no attachments in the new rev, there's nothing to do:
            IDictionary<string, object> revAttachments = null;
            var properties = rev.GetProperties ();
            if (properties != null)
            {
                revAttachments = properties.Get("_attachments").AsDictionary<string, object>();
            }

            if (revAttachments == null || revAttachments.Count == 0 || rev.IsDeleted())
            {
                return;
            }

            foreach (string name in revAttachments.Keys)
            {
                var attachment = attachments.Get(name);
                if (attachment != null)
                {
                    // Determine the revpos, i.e. generation # this was added in. Usually this is
                    // implicit, but a rev being pulled in replication will have it set already.
                    if (attachment.GetRevpos() == 0)
                    {
                        attachment.SetRevpos(generation);
                    }
                    else
                    {
                        if (attachment.GetRevpos() > generation)
                        {
                            Log.W(Tag, string.Format("Attachment {0} {1} has unexpected revpos {2}, setting to {3}", rev, name, attachment.GetRevpos(), generation));
                            attachment.SetRevpos(generation);
                        }
                    }
                    // Finally insert the attachment:
                    InsertAttachmentForSequence(attachment, newSequence);
                }
                else
                {
                    // It's just a stub, so copy the previous revision's attachment entry:
                    //? Should I enforce that the type and digest (if any) match?
                    CopyAttachmentNamedFromSequenceToSequence(name, parentSequence, newSequence);
                }
            }
        }
Exemple #6
0
		internal void StubOutAttachmentsInRevision(IDictionary<string, AttachmentInternal
			> attachments, RevisionInternal rev)
		{
			IDictionary<string, object> properties = rev.GetProperties();
			IDictionary<string, object> attachmentsFromProps = (IDictionary<string, object>)properties
				.Get("_attachments");
			if (attachmentsFromProps != null)
			{
				foreach (string attachmentKey in attachmentsFromProps.Keys)
				{
					IDictionary<string, object> attachmentFromProps = (IDictionary<string, object>)attachmentsFromProps
						.Get(attachmentKey);
					if (attachmentFromProps.Get("follows") != null || attachmentFromProps.Get("data")
						 != null)
					{
						Sharpen.Collections.Remove(attachmentFromProps, "follows");
						Sharpen.Collections.Remove(attachmentFromProps, "data");
						attachmentFromProps.Put("stub", true);
						if (attachmentFromProps.Get("revpos") == null)
						{
							attachmentFromProps.Put("revpos", rev.GetGeneration());
						}
						AttachmentInternal attachmentObject = attachments.Get(attachmentKey);
						if (attachmentObject != null)
						{
							attachmentFromProps.Put("length", attachmentObject.GetLength());
							if (attachmentObject.GetBlobKey() != null)
							{
								// case with Large Attachment
								attachmentFromProps.Put("digest", attachmentObject.GetBlobKey().Base64Digest());
							}
						}
						attachmentsFromProps.Put(attachmentKey, attachmentFromProps);
					}
				}
			}
		}
        public void TestPutLargeAttachment()
        {
            var testAttachmentName = "test_attachment";
            var attachments = database.Attachments;
            attachments.DeleteBlobs();
            Assert.AreEqual(0, attachments.Count());

            var status = new Status();
            var rev1Properties = new Dictionary<string, object>();
            rev1Properties["foo"] = 1;
            rev1Properties["bar"] = false;
            var rev1 = database.PutRevision(new RevisionInternal(rev1Properties, 
                database), null, false, status);
            Assert.AreEqual(StatusCode.Created, status.GetCode());
            var largeAttachment = new StringBuilder();
            for (int i = 0; i < Database.BigAttachmentLength; i++)
            {
                largeAttachment.Append("big attachment!");
            }

            var attach1 = Runtime.GetBytesForString(largeAttachment.ToString()).ToArray();
            database.InsertAttachmentForSequenceWithNameAndType(
                new ByteArrayInputStream(attach1), rev1.GetSequence(), 
                testAttachmentName, "text/plain", rev1.GetGeneration());
            var attachment = database.GetAttachmentForSequence(rev1.GetSequence(), testAttachmentName);
            Assert.AreEqual("text/plain", attachment.ContentType);
            var data = attachment.Content.ToArray();
            Assert.IsTrue(Arrays.Equals(attach1, data));

            const DocumentContentOptions contentOptions = DocumentContentOptions.IncludeAttachments | DocumentContentOptions.BigAttachmentsFollow;
            var attachmentDictForSequence = database.GetAttachmentsDictForSequenceWithContent(rev1.GetSequence(), contentOptions);
            var innerDict = (IDictionary<string, object>)attachmentDictForSequence[testAttachmentName];
            if (innerDict.ContainsKey("stub"))
            {
                if (((bool)innerDict["stub"]))
                {
                    throw new RuntimeException("Expected attachment dict 'stub' key to be true");
                } else {
                    throw new RuntimeException("Expected attachment dict to have 'stub' key");
                }
            }
            if (!innerDict.ContainsKey("follows"))
            {
                throw new RuntimeException("Expected attachment dict to have 'follows' key");
            }

            // Workaround :
            // Not closing the content stream will cause Sharing Violation
            // Exception when trying to get the same attachment going forward.
            attachment.ContentStream.Close();

            var rev1WithAttachments = database.GetDocumentWithIDAndRev(
                rev1.GetDocId(), rev1.GetRevId(), contentOptions);
            
            var rev1WithAttachmentsProperties = rev1WithAttachments.GetProperties();
            var rev2Properties = new Dictionary<string, object>();
            rev2Properties.Put("_id", rev1WithAttachmentsProperties["_id"]);
            rev2Properties["foo"] = 2;
            var newRev = new RevisionInternal(rev2Properties, database);
            var rev2 = database.PutRevision(newRev, rev1WithAttachments.GetRevId(), false, status);
            Assert.AreEqual(StatusCode.Created, status.GetCode());

            database.CopyAttachmentNamedFromSequenceToSequence(
                testAttachmentName, rev1WithAttachments.GetSequence(), rev2.GetSequence());

            // Check the 2nd revision's attachment:
            var rev2FetchedAttachment = database.GetAttachmentForSequence(rev2.GetSequence(), testAttachmentName);
            Assert.AreEqual(attachment.Length, rev2FetchedAttachment.Length);
            AssertPropertiesAreEqual(attachment.Metadata, rev2FetchedAttachment.Metadata);
            Assert.AreEqual(attachment.ContentType, rev2FetchedAttachment.ContentType);

            // Add a third revision of the same document:
            var rev3Properties = new Dictionary<string, object>();
            rev3Properties.Put("_id", rev2.GetProperties().Get("_id"));
            rev3Properties["foo"] = 3;
            rev3Properties["baz"] = false;
            var rev3 = new RevisionInternal(rev3Properties, database);
            rev3 = database.PutRevision(rev3, rev2.GetRevId(), false, status);

            Assert.AreEqual(StatusCode.Created, status.GetCode());
            var attach3 = Runtime.GetBytesForString("<html><blink>attach3</blink></html>").ToArray();
            database.InsertAttachmentForSequenceWithNameAndType(
                new ByteArrayInputStream(attach3), rev3.GetSequence(), 
                testAttachmentName, "text/html", rev3.GetGeneration());

            // Check the 3rd revision's attachment:
            var rev3FetchedAttachment = database.GetAttachmentForSequence(
                rev3.GetSequence(), testAttachmentName);
            data = rev3FetchedAttachment.Content.ToArray();
            Assert.IsTrue(Arrays.Equals(attach3, data));
            Assert.AreEqual("text/html", rev3FetchedAttachment.ContentType);

            // TODO: why doesn't this work?
            // Assert.assertEquals(attach3.length, rev3FetchedAttachment.getLength());
            ICollection<BlobKey> blobKeys = database.Attachments.AllKeys();
            Assert.AreEqual(2, blobKeys.Count);
            database.Compact();
            blobKeys = database.Attachments.AllKeys();
            Assert.AreEqual(1, blobKeys.Count);
        }
		/// <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);
		}