internal RevisionInternal GetParentRevision(RevisionInternal rev) { // First get the parent's sequence: var seq = rev.GetSequence(); if (seq > 0) { seq = LongForQuery("SELECT parent FROM revs WHERE sequence=?", new [] { Convert.ToString(seq) }); } else { var docNumericID = GetDocNumericID(rev.GetDocId()); if (docNumericID <= 0) { return null; } var args = new [] { Convert.ToString(docNumericID), rev.GetRevId() }; seq = LongForQuery("SELECT parent FROM revs WHERE doc_id=? and revid=?", args); } if (seq == 0) { return null; } // Now get its revID and deletion status: RevisionInternal result = null; var queryArgs = new [] { Convert.ToString(seq) }; var queryString = "SELECT revid, deleted FROM revs WHERE sequence=?"; Cursor cursor = null; try { cursor = StorageEngine.RawQuery(queryString, queryArgs); if (cursor.MoveToNext()) { string revId = cursor.GetString(0); bool deleted = (cursor.GetInt(1) > 0); result = new RevisionInternal(rev.GetDocId(), revId, deleted, this); result.SetSequence(seq); } } finally { cursor.Close(); } return result; }
/// <summary>Inserts the _id, _rev and _attachments properties into the JSON data and stores it in rev. /// </summary> /// <remarks> /// Inserts the _id, _rev and _attachments properties into the JSON data and stores it in rev. /// Rev must already have its revID and sequence properties set. /// </remarks> internal IDictionary<String, Object> ExtraPropertiesForRevision(RevisionInternal rev, DocumentContentOptions contentOptions) { var docId = rev.GetDocId(); var revId = rev.GetRevId(); var sequenceNumber = rev.GetSequence(); Debug.Assert((revId != null)); Debug.Assert((sequenceNumber > 0)); // Get attachment metadata, and optionally the contents: IDictionary<string, object> attachmentsDict = null; if (!contentOptions.HasFlag(DocumentContentOptions.NoAttachments)) { attachmentsDict = GetAttachmentsDictForSequenceWithContent (sequenceNumber, contentOptions); } // Get more optional stuff to put in the properties: //OPT: This probably ends up making redundant SQL queries if multiple options are enabled. var localSeq = -1L; if (contentOptions.HasFlag(DocumentContentOptions.IncludeLocalSeq)) { localSeq = sequenceNumber; } IDictionary<string, object> revHistory = null; if (contentOptions.HasFlag(DocumentContentOptions.IncludeRevs)) { revHistory = GetRevisionHistoryDict(rev); } IList<object> revsInfo = null; if (contentOptions.HasFlag(DocumentContentOptions.IncludeRevsInfo)) { revsInfo = new AList<object>(); var revHistoryFull = GetRevisionHistory(rev); foreach (RevisionInternal historicalRev in revHistoryFull) { var revHistoryItem = new Dictionary<string, object>(); var status = "available"; if (historicalRev.IsDeleted()) { status = "deleted"; } if (historicalRev.IsMissing()) { status = "missing"; } revHistoryItem.Put("rev", historicalRev.GetRevId()); revHistoryItem["status"] = status; revsInfo.AddItem(revHistoryItem); } } IList<string> conflicts = null; if (contentOptions.HasFlag(DocumentContentOptions.IncludeConflicts)) { var revs = GetAllRevisionsOfDocumentID(docId, true); if (revs.Count > 1) { conflicts = new AList<string>(); foreach (RevisionInternal savedRev in revs) { if (!(savedRev.Equals(rev) || savedRev.IsDeleted())) { conflicts.AddItem(savedRev.GetRevId()); } } } } var result = new Dictionary<string, object>(); result["_id"] = docId; result["_rev"] = revId; if (rev.IsDeleted()) { result["_deleted"] = true; } if (attachmentsDict != null) { result["_attachments"] = attachmentsDict; } if (localSeq > -1) { result["_local_seq"] = localSeq; } if (revHistory != null) { result["_revisions"] = revHistory; } if (revsInfo != null) { result["_revs_info"] = revsInfo; } if (conflicts != null) { result["_conflicts"] = conflicts; } return result; }
//Doesn't handle CouchbaseLiteException internal RevisionInternal LoadRevisionBody(RevisionInternal rev) { if (!IsOpen) { Log.W(TAG, "LoadRevisionBody called on closed database"); return null; } if (rev.GetSequence() > 0) { var props = rev.GetProperties(); if (props != null && props.GetCast<string>("_rev") != null && props.GetCast<string>("_id") != null) { return rev; } } Debug.Assert(rev.GetDocId() != null && rev.GetRevId() != null); Storage.LoadRevisionBody(rev); return rev; }
// Apply the options in the URL query to the specified revision and create a new revision object internal static RevisionInternal ApplyOptions(DocumentContentOptions options, RevisionInternal rev, ICouchbaseListenerContext context, Database db, Status outStatus) { if ((options & (DocumentContentOptions.IncludeRevs | DocumentContentOptions.IncludeRevsInfo | DocumentContentOptions.IncludeConflicts | DocumentContentOptions.IncludeAttachments | DocumentContentOptions.IncludeLocalSeq)) != 0) { var dst = rev.GetProperties(); if (options.HasFlag(DocumentContentOptions.IncludeLocalSeq)) { dst["_local_seq"] = rev.GetSequence(); } if (options.HasFlag(DocumentContentOptions.IncludeRevs)) { var revs = db.GetRevisionHistory(rev, null); dst["_revisions"] = Database.MakeRevisionHistoryDict(revs); } if (options.HasFlag(DocumentContentOptions.IncludeRevsInfo)) { dst["_revs_info"] = db.Storage.GetRevisionHistory(rev, null).Select(x => { string status = "available"; if(x.IsDeleted()) { status = "deleted"; } else if(x.IsMissing()) { status = "missing"; } return new Dictionary<string, object> { { "rev", x.GetRevId() }, { "status", status } }; }); } if (options.HasFlag(DocumentContentOptions.IncludeConflicts)) { RevisionList revs = db.Storage.GetAllDocumentRevisions(rev.GetDocId(), true); if (revs.Count > 1) { dst["_conflicts"] = revs.Select(x => { return x.Equals(rev) || x.IsDeleted() ? null : x.GetRevId(); }); } } RevisionInternal nuRev = new RevisionInternal(dst); if (options.HasFlag(DocumentContentOptions.IncludeAttachments)) { bool attEncodingInfo = context != null && context.GetQueryParam<bool>("att_encoding_info", bool.TryParse, false); if(!db.ExpandAttachments(nuRev, 0, false, !attEncodingInfo, outStatus)) { return null; } } rev = nuRev; } return rev; }
internal RevisionInternal RevisionByLoadingBody(RevisionInternal rev, Status outStatus) { // First check for no-op -- if we just need the default properties and already have them: if (rev.GetSequence() != 0) { var props = rev.GetProperties(); if (props != null && props.ContainsKey("_rev") && props.ContainsKey("_id")) { if (outStatus != null) { outStatus.Code = StatusCode.Ok; } return rev; } } RevisionInternal nuRev = rev.CopyWithDocID(rev.GetDocId(), rev.GetRevId()); try { LoadRevisionBody(nuRev); } catch(CouchbaseLiteException e) { if (outStatus != null) { outStatus.Code = e.CBLStatus.Code; } nuRev = null; } return nuRev; }
private void RemovePending(RevisionInternal revisionInternal) { long seq = revisionInternal.GetSequence(); if (pendingSequences == null || pendingSequences.Count == 0) { Log.W(Log.TagSync, "%s: removePending() called w/ rev: %s, but pendingSequences empty" , this, revisionInternal); return; } bool wasFirst = (seq == pendingSequences.First()); if (!pendingSequences.Contains(seq)) { Log.W(Log.TagSync, "%s: removePending: sequence %s not in set, for rev %s", this, seq, revisionInternal); } pendingSequences.Remove(seq); if (wasFirst) { // If I removed the first pending sequence, can advance the checkpoint: long maxCompleted; if (pendingSequences.Count == 0) { maxCompleted = maxPendingSequence; } else { maxCompleted = pendingSequences.First(); --maxCompleted; } SetLastSequence(System.Convert.ToString(maxCompleted)); } }
public RevisionInternal LoadRevisionBody(RevisionInternal rev, EnumSet<Database.TDContentOptions > contentOptions) { if (rev.GetBody() != null && contentOptions == EnumSet.NoneOf<Database.TDContentOptions >() && rev.GetSequence() != 0) { return rev; } System.Diagnostics.Debug.Assert(((rev.GetDocId() != null) && (rev.GetRevId() != null ))); Cursor cursor = null; Status result = new Status(Status.NotFound); try { // TODO: on ios this query is: // TODO: "SELECT sequence, json FROM revs WHERE doc_id=? AND revid=? LIMIT 1" string sql = "SELECT sequence, json FROM revs, docs WHERE revid=? AND docs.docid=? AND revs.doc_id=docs.doc_id LIMIT 1"; string[] args = new string[] { rev.GetRevId(), rev.GetDocId() }; cursor = database.RawQuery(sql, args); if (cursor.MoveToNext()) { result.SetCode(Status.Ok); rev.SetSequence(cursor.GetLong(0)); ExpandStoredJSONIntoRevisionWithAttachments(cursor.GetBlob(1), rev, contentOptions ); } } catch (SQLException e) { Log.E(Database.Tag, "Error loading revision body", e); throw new CouchbaseLiteException(Status.InternalServerError); } finally { if (cursor != null) { cursor.Close(); } } if (result.GetCode() == Status.NotFound) { throw new CouchbaseLiteException(result); } return rev; }
/// <summary> /// Creates a dictionary of metadata for one specific revision /// </summary> /// <returns>The metadata dictionary</returns> /// <param name="rev">The revision to examine</param> /// <param name="responseState">The current response state</param> public static IDictionary<string, object> ChangesDictForRev(RevisionInternal rev, DBMonitorCouchbaseResponseState responseState) { if (responseState.ChangesIncludeDocs) { var status = new Status(); var rev2 = DocumentMethods.ApplyOptions(responseState.ContentOptions, rev, responseState.Context, responseState.Db, status); if (rev2 != null) { rev2.SetSequence(rev.GetSequence()); rev = rev2; } } return new NonNullDictionary<string, object> { { "seq", rev.GetSequence() }, { "id", rev.GetDocId() }, { "changes", new List<object> { new Dictionary<string, object> { { "rev", rev.GetRevId() } } } }, { "deleted", rev.IsDeleted() ? (object)true : null }, { "doc", responseState.ChangesIncludeDocs ? rev.GetProperties() : null } }; }
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); }
public IDictionary<string, object> ExtraPropertiesForRevision(RevisionInternal rev , EnumSet<Database.TDContentOptions> contentOptions) { string docId = rev.GetDocId(); string revId = rev.GetRevId(); long sequenceNumber = rev.GetSequence(); System.Diagnostics.Debug.Assert((revId != null)); System.Diagnostics.Debug.Assert((sequenceNumber > 0)); // Get attachment metadata, and optionally the contents: IDictionary<string, object> attachmentsDict = GetAttachmentsDictForSequenceWithContent (sequenceNumber, contentOptions); // Get more optional stuff to put in the properties: //OPT: This probably ends up making redundant SQL queries if multiple options are enabled. long localSeq = null; if (contentOptions.Contains(Database.TDContentOptions.TDIncludeLocalSeq)) { localSeq = sequenceNumber; } IDictionary<string, object> revHistory = null; if (contentOptions.Contains(Database.TDContentOptions.TDIncludeRevs)) { revHistory = GetRevisionHistoryDict(rev); } IList<object> revsInfo = null; if (contentOptions.Contains(Database.TDContentOptions.TDIncludeRevsInfo)) { revsInfo = new AList<object>(); IList<RevisionInternal> revHistoryFull = GetRevisionHistory(rev); foreach (RevisionInternal historicalRev in revHistoryFull) { IDictionary<string, object> revHistoryItem = new Dictionary<string, object>(); string status = "available"; if (historicalRev.IsDeleted()) { status = "deleted"; } if (historicalRev.IsMissing()) { status = "missing"; } revHistoryItem.Put("rev", historicalRev.GetRevId()); revHistoryItem.Put("status", status); revsInfo.AddItem(revHistoryItem); } } IList<string> conflicts = null; if (contentOptions.Contains(Database.TDContentOptions.TDIncludeConflicts)) { RevisionList revs = GetAllRevisionsOfDocumentID(docId, true); if (revs.Count > 1) { conflicts = new AList<string>(); foreach (RevisionInternal historicalRev in revs) { if (!historicalRev.Equals(rev)) { conflicts.AddItem(historicalRev.GetRevId()); } } } } IDictionary<string, object> result = new Dictionary<string, object>(); result.Put("_id", docId); result.Put("_rev", revId); if (rev.IsDeleted()) { result.Put("_deleted", true); } if (attachmentsDict != null) { result.Put("_attachments", attachmentsDict); } if (localSeq != null) { result.Put("_local_seq", localSeq); } if (revHistory != null) { result.Put("_revisions", revHistory); } if (revsInfo != null) { result.Put("_revs_info", revsInfo); } if (conflicts != null) { result.Put("_conflicts", conflicts); } return result; }
/// <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); }
private void RemovePending(RevisionInternal revisionInternal) { lock (pendingSequences) { var seq = revisionInternal.GetSequence(); var wasFirst = (seq == pendingSequences.FirstOrDefault()); if (!pendingSequences.Contains(seq)) { Log.W(Tag, "Remove Pending: Sequence " + seq + " not in set, for rev " + revisionInternal); } pendingSequences.Remove(seq); if (wasFirst) { // If removing the first pending sequence, can advance the checkpoint: long maxCompleted; if (pendingSequences.Count == 0) { maxCompleted = maxPendingSequence; } else { maxCompleted = pendingSequences.First(); --maxCompleted; } LastSequence = maxCompleted.ToString(); } } }
private void AddPending(RevisionInternal revisionInternal) { lock(pendingSequences) { var seq = revisionInternal.GetSequence(); pendingSequences.Add(seq); if (seq > maxPendingSequence) { maxPendingSequence = seq; } } }
/// <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); } } }
public RevisionInternal UpdateAttachment(string filename, InputStream contentStream , string contentType, string docID, string oldRevID) { bool isSuccessful = false; if (filename == null || filename.Length == 0 || (contentStream != null && contentType == null) || (oldRevID != null && docID == null) || (contentStream != null && docID == null)) { throw new CouchbaseLiteException(Status.BadRequest); } BeginTransaction(); try { RevisionInternal oldRev = new RevisionInternal(docID, oldRevID, false, this); if (oldRevID != null) { // Load existing revision if this is a replacement: try { LoadRevisionBody(oldRev, EnumSet.NoneOf<Database.TDContentOptions>()); } catch (CouchbaseLiteException e) { if (e.GetCBLStatus().GetCode() == Status.NotFound && ExistsDocumentWithIDAndRev(docID , null)) { throw new CouchbaseLiteException(Status.Conflict); } } IDictionary<string, object> oldRevProps = oldRev.GetProperties(); IDictionary<string, object> attachments = null; if (oldRevProps != null) { attachments = (IDictionary<string, object>)oldRevProps.Get("_attachments"); } if (contentStream == null && attachments != null && !attachments.ContainsKey(filename )) { throw new CouchbaseLiteException(Status.NotFound); } // Remove the _attachments stubs so putRevision: doesn't copy the rows for me // OPT: Would be better if I could tell loadRevisionBody: not to add it if (attachments != null) { IDictionary<string, object> properties = new Dictionary<string, object>(oldRev.GetProperties ()); Sharpen.Collections.Remove(properties, "_attachments"); oldRev.SetBody(new Body(properties)); } } else { // If this creates a new doc, it needs a body: oldRev.SetBody(new Body(new Dictionary<string, object>())); } // Create a new revision: Status putStatus = new Status(); RevisionInternal newRev = PutRevision(oldRev, oldRevID, false, putStatus); if (newRev == null) { return null; } if (oldRevID != null) { // Copy all attachment rows _except_ for the one being updated: string[] args = new string[] { System.Convert.ToString(newRev.GetSequence()), System.Convert.ToString (oldRev.GetSequence()), filename }; database.ExecSQL("INSERT INTO attachments " + "(sequence, filename, key, type, length, revpos) " + "SELECT ?, filename, key, type, length, revpos FROM attachments " + "WHERE sequence=? AND filename != ?" , args); } if (contentStream != null) { // If not deleting, add a new attachment entry: InsertAttachmentForSequenceWithNameAndType(contentStream, newRev.GetSequence(), filename , contentType, newRev.GetGeneration()); } isSuccessful = true; return newRev; } catch (SQLException e) { Log.E(Tag, "Error updating attachment", e); throw new CouchbaseLiteException(new Status(Status.InternalServerError)); } finally { EndTransaction(isSuccessful); } }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal RevisionInternal LoadRevisionBody(RevisionInternal rev, DocumentContentOptions contentOptions) { if (rev.GetBody() != null && contentOptions == DocumentContentOptions.None && rev.GetSequence() != 0) { return rev; } if ((rev.GetDocId() == null) || (rev.GetRevId() == null)) { Log.E(Database.Tag, "Error loading revision body"); throw new CouchbaseLiteException(StatusCode.PreconditionFailed); } Cursor cursor = null; var result = new Status(StatusCode.NotFound); try { // TODO: on ios this query is: // TODO: "SELECT sequence, json FROM revs WHERE doc_id=@ AND revid=@ LIMIT 1" var sql = "SELECT sequence, json FROM revs, docs WHERE revid=? AND docs.docid=? AND revs.doc_id=docs.doc_id LIMIT 1"; var args = new [] { rev.GetRevId(), rev.GetDocId() }; cursor = StorageEngine.RawQuery(sql, CommandBehavior.SequentialAccess, args); if (cursor.MoveToNext()) { result.SetCode(StatusCode.Ok); rev.SetSequence(cursor.GetLong(0)); ExpandStoredJSONIntoRevisionWithAttachments(cursor.GetBlob(1), rev, contentOptions); } } catch (SQLException e) { Log.E(Tag, "Error loading revision body", e); throw new CouchbaseLiteException(StatusCode.InternalServerError); } finally { if (cursor != null) { cursor.Close(); } } if (result.GetCode() == StatusCode.NotFound) { throw new CouchbaseLiteException(result.GetCode()); } return rev; }
private void AddPending(RevisionInternal revisionInternal) { lock(_pendingSequences) { var seq = revisionInternal.GetSequence(); if (!_pendingSequences.ContainsKey(seq)) { _pendingSequences.Add(seq, 0); } if (seq > _maxPendingSequence) { _maxPendingSequence = seq; } } }
/// <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; } } } } }
private void RemovePending(RevisionInternal revisionInternal) { lock (_pendingSequences) { var seq = revisionInternal.GetSequence(); var wasFirst = (_pendingSequences.Count > 0 && seq == _pendingSequences.ElementAt(0).Key); if (!_pendingSequences.ContainsKey(seq)) { Log.W(TAG, "Remove Pending: Sequence " + seq + " not in set, for rev " + revisionInternal); } _pendingSequences.Remove(seq); if (wasFirst) { // If removing the first pending sequence, can advance the checkpoint: long maxCompleted; if (_pendingSequences.Count == 0) { maxCompleted = _maxPendingSequence; } else { maxCompleted = _pendingSequences.ElementAt(0).Key; --maxCompleted; } LastSequence = maxCompleted.ToString(); } if (_pendingSequences.Count == 0) { FireTrigger(Continuous ? ReplicationTrigger.WaitingForChanges : ReplicationTrigger.StopGraceful); } } }
//Doesn't handle CouchbaseLiteException internal RevisionInternal LoadRevisionBody(RevisionInternal rev) { if (rev.GetSequence() > 0) { var props = rev.GetProperties(); if (props != null && props.GetCast<string>("_rev") != null && props.GetCast<string>("_id") != null) { return rev; } } Debug.Assert(rev.GetDocId() != null && rev.GetRevId() != null); Storage.LoadRevisionBody(rev); return rev; }
private void AddPending(RevisionInternal revisionInternal) { long seq = revisionInternal.GetSequence(); pendingSequences.AddItem(seq); if (seq > maxPendingSequence) { maxPendingSequence = seq; } }