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