public SavedRevision Update(Document.DocumentUpdater updater) { int lastErrorCode = Status.Unknown; do { UnsavedRevision newRev = CreateRevision(); if (updater.Update(newRev) == false) { break; } try { SavedRevision savedRev = newRev.Save(); if (savedRev != null) { return(savedRev); } } catch (CouchbaseLiteException e) { lastErrorCode = e.GetCBLStatus().GetCode(); } }while (lastErrorCode == Status.Conflict); return(null); }
/// <summary>Saves a new revision by letting the caller update the existing properties. /// </summary> /// <remarks> /// Saves a new revision by letting the caller update the existing properties. /// This method handles conflicts by retrying (calling the block again). /// The DocumentUpdater implementation should modify the properties of the new revision and return YES to save or /// NO to cancel. Be careful: the DocumentUpdater can be called multiple times if there is a conflict! /// </remarks> /// <param name="updateDelegate"> /// the callback DocumentUpdater implementation. Will be called on each /// attempt to save. Should update the given revision's properties and then /// return YES, or just return NO to cancel. /// </param> /// <returns>The new saved revision, or null on error or cancellation.</returns> /// <exception cref="CouchbaseLiteException">CouchbaseLiteException</exception> /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> public SavedRevision Update(UpdateDelegate updateDelegate) { Debug.Assert(updateDelegate != null); var lastErrorCode = StatusCode.Unknown; do { UnsavedRevision newRev = CreateRevision(); if (!updateDelegate(newRev)) { break; } try { SavedRevision savedRev = newRev.Save(); if (savedRev != null) { return(savedRev); } } catch (CouchbaseLiteException e) { lastErrorCode = e.GetCBLStatus().GetCode(); } }while (lastErrorCode == StatusCode.Conflict); return(null); }
/// <summary> /// Replaces the current revision's attachments with copies of the /// attachments from another revision. /// </summary> /// <param name="revision">The revision.</param> /// <param name="sourceRevision">The revision with the attachments to be copied.</param> public static void ReplaceAttachmentsFrom(this UnsavedRevision revision, Revision sourceRevision) { Covenant.Requires <ArgumentNullException>(revision != null); Covenant.Requires <ArgumentNullException>(sourceRevision != null); // Remove the current attachments. foreach (var name in revision.AttachmentNames.ToArray()) { revision.RemoveAttachment(name); } // Copy the source attachments. foreach (var sourceAttachment in sourceRevision.Attachments) { // $todo(jeff.lill): // // I'm a little nervous about this call. [Attachment.Content] is going to // load the attachment into memory, which could be a significant overhead. // It's possible to pass a stream, but looking at the Couchbase Lite source, // it appears that the new attachment would try to take ownership of the // source attachment's stream (if I passed it), probably ending badly. revision.SetAttachment(sourceAttachment.Name, sourceAttachment.ContentType, sourceAttachment.Content); } }
/// <summary> /// Merges the attachments from another revision. /// </summary> /// <param name="revision">The revision.</param> /// <param name="sourceRevision">The revision with the attachments to be merged.</param> /// <param name="keepMine">Controls what happens when the source and target have the same attachment (see remarks).</param> /// <remarks> /// <para> /// The <paramref name="keepMine"/> parameter determines what happens when the source and target /// revisions has an attachment with the same name. When <paramref name="keepMine"/>=<c>true</c> /// (the default), then matching attachments from the source revision will be ignored. /// </para> /// <para> /// When <paramref name="keepMine"/>=<c>false</c>, matching attachments from the source /// revision will be copied to the current revision, overwriting the existion attachment. /// </para> /// </remarks> public static void MergeAttachmentsFrom(this UnsavedRevision revision, Revision sourceRevision, bool keepMine = true) { Covenant.Requires <ArgumentNullException>(revision != null); Covenant.Requires <ArgumentNullException>(sourceRevision != null); // Merge the source attachments. foreach (var sourceAttachment in sourceRevision.Attachments) { var existingAttachment = revision.GetAttachment(sourceAttachment.Name); if (existingAttachment != null && keepMine) { continue; } // $todo(jeff.lill): // // I'm a little nervous about this call. [Attachment.Content] is going to // load the attachment into memory, which could be a significant overhead. // It's possible to pass a stream, but looking at the Couchbase Lite source, // it appears that the new attachment would try to take ownership of the // source attachment's stream (if I passed it), probably ending badly. revision.SetAttachment(sourceAttachment.Name, sourceAttachment.ContentType, sourceAttachment.Content); } }
/// <summary>https://github.com/couchbase/couchbase-lite-java-core/issues/164</summary> /// <exception cref="System.Exception"></exception> public virtual void TestRevisionIdEquivalentRevisions() { // two revisions with the same content and the same json // should have the exact same revision id, because their content // will have an identical hash IDictionary <string, object> properties = new Dictionary <string, object>(); properties.Put("testName", "testCreateRevisions"); properties.Put("tag", 1337); IDictionary <string, object> properties2 = new Dictionary <string, object>(); properties2.Put("testName", "testCreateRevisions"); properties2.Put("tag", 1338); Document doc = database.CreateDocument(); UnsavedRevision newRev = doc.CreateRevision(); newRev.SetUserProperties(properties); SavedRevision rev1 = newRev.Save(); UnsavedRevision newRev2a = rev1.CreateRevision(); newRev2a.SetUserProperties(properties2); SavedRevision rev2a = newRev2a.Save(); UnsavedRevision newRev2b = rev1.CreateRevision(); newRev2b.SetUserProperties(properties2); SavedRevision rev2b = newRev2b.Save(true); NUnit.Framework.Assert.AreEqual(rev2a.GetId(), rev2b.GetId()); }
/// <summary>Regression test for https://github.com/couchbase/couchbase-lite-android-core/issues/70 /// </summary> /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> /// <exception cref="System.IO.IOException"></exception> public virtual void TestAttachmentDisappearsAfterSave() { // create a doc with an attachment Document doc = database.CreateDocument(); string content = "This is a test attachment!"; ByteArrayInputStream body = new ByteArrayInputStream(Sharpen.Runtime.GetBytesForString (content)); UnsavedRevision rev = doc.CreateRevision(); rev.SetAttachment("index.html", "text/plain; charset=utf-8", body); rev.Save(); // make sure the doc's latest revision has the attachment IDictionary <string, object> attachments = (IDictionary)doc.GetCurrentRevision().GetProperty ("_attachments"); NUnit.Framework.Assert.IsNotNull(attachments); NUnit.Framework.Assert.AreEqual(1, attachments.Count); // make sure the rev has the attachment attachments = (IDictionary)rev.GetProperty("_attachments"); NUnit.Framework.Assert.IsNotNull(attachments); NUnit.Framework.Assert.AreEqual(1, attachments.Count); // create new properties to add IDictionary <string, object> properties = new Dictionary <string, object>(); properties.Put("foo", "bar"); // make sure the new rev still has the attachment UnsavedRevision rev2 = doc.CreateRevision(); rev2.GetProperties().PutAll(properties); rev2.Save(); attachments = (IDictionary)rev2.GetProperty("_attachments"); NUnit.Framework.Assert.IsNotNull(attachments); NUnit.Framework.Assert.AreEqual(1, attachments.Count); }
public static void AddImageAttachment(UnsavedRevision rev, string name, Image img) { if (img == null) { if (rev.AttachmentNames.Contains (name)) { rev.RemoveAttachment (name); } } else { rev.SetAttachment (name, "image/png", img.Serialize()); } }
/// <exception cref="System.Exception"></exception> public static SavedRevision CreateRevisionWithRandomProps(SavedRevision createRevFrom , bool allowConflict) { IDictionary <string, object> properties = new Dictionary <string, object>(); properties.Put(UUID.RandomUUID().ToString(), "val"); UnsavedRevision unsavedRevision = createRevFrom.CreateRevision(); unsavedRevision.SetUserProperties(properties); return(unsavedRevision.Save(allowConflict)); }
/// <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["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((IDictionary <string, object> document, EmitDelegate emitter) => { string id = (string)document["_id"]; emitter.Emit(id, null); }, null, "1"); // try to get the attachment Query query = view.CreateQuery(); query.Prefetch = true; QueryEnumerator results = query.Run(); while (results.MoveNext()) { QueryRow row = results.Current; // This returns the revision just fine, but the sequence number // is set to 0. SavedRevision revision = row.Document.CurrentRevision; IList <string> attachments = revision.AttachmentNames; // 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 StatusCode.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); } }
/// <summary> /// Marks the revision as deleted while optionally retaining non-reserved properties. /// </summary> /// <param name="revision">The revision.</param> /// <param name="keepProperties"> /// Optionally indicates that the non-reserved properties should be retained. /// This defaults to <c>false</c>. /// </param> public static void Delete(this UnsavedRevision revision, bool keepProperties = false) { Covenant.Requires <ArgumentNullException>(revision != null); revision.IsDeletion = true; if (!keepProperties) { foreach (var nonReservedKey in revision.Properties.Keys.Where(k => !k.StartsWith("_")).ToArray()) { revision.Properties.Remove(nonReservedKey); } } }
/// <summary>https://github.com/couchbase/couchbase-lite-java-core/issues/106</summary> /// <exception cref="System.Exception"></exception> public virtual void TestResolveConflict() { IDictionary <string, object> properties = new Dictionary <string, object>(); properties.Put("testName", "testCreateRevisions"); properties.Put("tag", 1337); // Create a conflict on purpose Document doc = database.CreateDocument(); UnsavedRevision newRev1 = doc.CreateRevision(); newRev1.SetUserProperties(properties); SavedRevision rev1 = newRev1.Save(); SavedRevision rev2a = CreateRevisionWithRandomProps(rev1, false); SavedRevision rev2b = CreateRevisionWithRandomProps(rev1, true); SavedRevision winningRev = null; SavedRevision losingRev = null; if (doc.GetCurrentRevisionId().Equals(rev2a.GetId())) { winningRev = rev2a; losingRev = rev2b; } else { winningRev = rev2b; losingRev = rev2a; } NUnit.Framework.Assert.AreEqual(2, doc.GetConflictingRevisions().Count); NUnit.Framework.Assert.AreEqual(2, doc.GetLeafRevisions().Count); // let's manually choose the losing rev as the winner. First, delete winner, which will // cause losing rev to be the current revision. SavedRevision deleteRevision = winningRev.DeleteDocument(); IList <SavedRevision> conflictingRevisions = doc.GetConflictingRevisions(); NUnit.Framework.Assert.AreEqual(1, conflictingRevisions.Count); NUnit.Framework.Assert.AreEqual(2, doc.GetLeafRevisions().Count); NUnit.Framework.Assert.AreEqual(3, deleteRevision.GetGeneration()); NUnit.Framework.Assert.AreEqual(losingRev.GetId(), doc.GetCurrentRevision().GetId ()); // Finally create a new revision rev3 based on losing rev SavedRevision rev3 = CreateRevisionWithRandomProps(losingRev, true); NUnit.Framework.Assert.AreEqual(rev3.GetId(), doc.GetCurrentRevisionId()); IList <SavedRevision> conflictingRevisions1 = doc.GetConflictingRevisions(); NUnit.Framework.Assert.AreEqual(1, conflictingRevisions1.Count); NUnit.Framework.Assert.AreEqual(2, doc.GetLeafRevisions().Count); }
/// <exception cref="System.Exception"></exception> public virtual void TestWinningRevIDOfDoc() { IDictionary <string, object> properties = new Dictionary <string, object>(); properties.Put("testName", "testCreateRevisions"); properties.Put("tag", 1337); IDictionary <string, object> properties2a = new Dictionary <string, object>(); properties2a.Put("testName", "testCreateRevisions"); properties2a.Put("tag", 1338); IDictionary <string, object> properties2b = new Dictionary <string, object>(); properties2b.Put("testName", "testCreateRevisions"); properties2b.Put("tag", 1339); IList <bool> outIsDeleted = new AList <bool>(); IList <bool> outIsConflict = new AList <bool>(); // Create a conflict on purpose Document doc = database.CreateDocument(); UnsavedRevision newRev1 = doc.CreateRevision(); newRev1.SetUserProperties(properties); SavedRevision rev1 = newRev1.Save(); long docNumericId = database.GetDocNumericID(doc.GetId()); NUnit.Framework.Assert.IsTrue(docNumericId != 0); NUnit.Framework.Assert.AreEqual(rev1.GetId(), database.WinningRevIDOfDoc(docNumericId , outIsDeleted, outIsConflict)); NUnit.Framework.Assert.IsTrue(outIsConflict.Count == 0); outIsDeleted = new AList <bool>(); outIsConflict = new AList <bool>(); UnsavedRevision newRev2a = rev1.CreateRevision(); newRev2a.SetUserProperties(properties2a); SavedRevision rev2a = newRev2a.Save(); NUnit.Framework.Assert.AreEqual(rev2a.GetId(), database.WinningRevIDOfDoc(docNumericId , outIsDeleted, outIsConflict)); NUnit.Framework.Assert.IsTrue(outIsConflict.Count == 0); outIsDeleted = new AList <bool>(); outIsConflict = new AList <bool>(); UnsavedRevision newRev2b = rev1.CreateRevision(); newRev2b.SetUserProperties(properties2b); SavedRevision rev2b = newRev2b.Save(true); database.WinningRevIDOfDoc(docNumericId, outIsDeleted, outIsConflict); NUnit.Framework.Assert.IsTrue(outIsConflict.Count > 0); }
/// <summary>https://github.com/couchbase/couchbase-lite-java-core/issues/164</summary> /// <exception cref="System.Exception"></exception> public virtual void TestRevisionIdDifferentRevisions() { // two revisions with different json should have different rev-id's // because their content will have a different hash (even though // they have the same generation number) IDictionary <string, object> properties = new Dictionary <string, object>(); properties.Put("testName", "testCreateRevisions"); properties.Put("tag", 1337); Document doc = database.CreateDocument(); UnsavedRevision newRev = doc.CreateRevision(); newRev.SetUserProperties(properties); SavedRevision rev1 = newRev.Save(); SavedRevision rev2a = CreateRevisionWithRandomProps(rev1, false); SavedRevision rev2b = CreateRevisionWithRandomProps(rev1, true); NUnit.Framework.Assert.AreNotSame(rev2a.GetId(), rev2b.GetId()); }
/// <summary> /// Creates and saves a new <see cref="Couchbase.Lite.Revision"/> by allowing the caller to update /// the existing properties. Conflicts are handled by calling the delegate again. /// </summary> /// <remarks> /// Saves a new revision by letting the caller update the existing properties. /// This method handles conflicts by retrying (calling the block again). /// The DocumentUpdater implementation should modify the properties of the new revision and return YES to save or /// NO to cancel. Be careful: the DocumentUpdater can be called multiple times if there is a conflict! /// </remarks> /// <param name="updateDelegate"> /// The delegate that will be called to update the new <see cref="Couchbase.Lite.Revision"/>'s properties. /// return YES, or just return NO to cancel. /// </param> /// <returns>The new <see cref="Couchbase.Lite.SavedRevision"/>, or null on error or cancellation.</returns> /// <exception cref="Couchbase.Lite.CouchbaseLiteException"> /// Thrown if an error occurs while creating or saving the new <see cref="Couchbase.Lite.Revision"/>. /// </exception> public SavedRevision Update(UpdateDelegate updateDelegate) { Debug.Assert(updateDelegate != null); if (updateDelegate == null) { return(null); } var lastErrorCode = StatusCode.Unknown; do { // Force the database to load the current revision // from disk, which will happen when CreateRevision // sees that currentRevision is null. if (lastErrorCode == StatusCode.Conflict) { currentRevision = null; } using (UnsavedRevision newRev = CreateRevision()) { if (!updateDelegate(newRev)) { break; } try { SavedRevision savedRev = newRev.Save(); if (savedRev != null) { return(savedRev); } } catch (CouchbaseLiteException e) { lastErrorCode = e.CBLStatus.Code; } } } while(lastErrorCode == StatusCode.Conflict); return(null); }
/// <exception cref="System.Exception"></exception> public static Document CreateDocWithAttachment(Database database, string attachmentName , string content) { IDictionary <string, object> properties = new Dictionary <string, object>(); properties.Put("foo", "bar"); Document doc = CreateDocumentWithProperties(database, properties); SavedRevision rev = doc.GetCurrentRevision(); NUnit.Framework.Assert.AreEqual(rev.GetAttachments().Count, 0); NUnit.Framework.Assert.AreEqual(rev.GetAttachmentNames().Count, 0); NUnit.Framework.Assert.IsNull(rev.GetAttachment(attachmentName)); ByteArrayInputStream body = new ByteArrayInputStream(Sharpen.Runtime.GetBytesForString (content)); UnsavedRevision rev2 = doc.CreateRevision(); rev2.SetAttachment(attachmentName, "text/plain; charset=utf-8", body); SavedRevision rev3 = rev2.Save(); NUnit.Framework.Assert.IsNotNull(rev3); NUnit.Framework.Assert.AreEqual(rev3.GetAttachments().Count, 1); NUnit.Framework.Assert.AreEqual(rev3.GetAttachmentNames().Count, 1); Attachment attach = rev3.GetAttachment(attachmentName); NUnit.Framework.Assert.IsNotNull(attach); NUnit.Framework.Assert.AreEqual(doc, attach.GetDocument()); NUnit.Framework.Assert.AreEqual(attachmentName, attach.GetName()); IList <string> attNames = new AList <string>(); attNames.AddItem(attachmentName); NUnit.Framework.Assert.AreEqual(rev3.GetAttachmentNames(), attNames); NUnit.Framework.Assert.AreEqual("text/plain; charset=utf-8", attach.GetContentType ()); NUnit.Framework.Assert.AreEqual(IOUtils.ToString(attach.GetContent(), "UTF-8"), content ); NUnit.Framework.Assert.AreEqual(Sharpen.Runtime.GetBytesForString(content).Length , attach.GetLength()); return(doc); }
protected void EmitBaseData(UnsavedRevision rev, SaveData dataToSave) { if (dataToSave.HasFlag (SaveData.PositionXY)) { var pos = transform.position; rev.Properties ["CouchbaseObject.Position.X"] = pos.x; rev.Properties ["CouchbaseObject.Position.Y"] = pos.y; if(dataToSave.HasFlag(SaveData.PositionZ)) { rev.Properties ["CouchbaseObject.Position.Z"] = pos.z; } } if (dataToSave.HasFlag (SaveData.Rotation)) { transform.rotation.InsertIntoDictionary(rev.Properties, ROTATION_KEYS); } if (dataToSave.HasFlag (SaveData.Scale)) { transform.localScale.InsertIntoDictionary(rev.Properties, SCALE_KEYS); } if (dataToSave.HasFlag (SaveData.Color)) { gameObject.GetComponent<MeshRenderer>().material.color.InsertIntoDictionary(rev.Properties, COLOR_KEYS); } }
/// <summary> /// Creates a new <see cref="Couchbase.Lite.UnsavedRevision"/> whose properties and attachments are initially identical to this one. /// </summary> /// <remarks> /// Creates a new mutable child revision whose properties and attachments are initially identical /// to this one's, which you can modify and then save. /// </remarks> /// <returns> /// A new child <see cref="Couchbase.Lite.UnsavedRevision"/> whose properties and attachments /// are initially identical to this one. /// </returns> public UnsavedRevision CreateRevision() { var newRevision = new UnsavedRevision(Document, this); return newRevision; }
protected override bool UpdateRevision (UnsavedRevision rev) { rev.Properties ["type"] = "CouchbaseSphere"; EmitBaseData (rev, SaveData.Position | SaveData.Color); return true; }
/// <summary> /// Creates a new <see cref="Couchbase.Lite.UnsavedRevision"/> whose properties and attachments are initially identical to this one. /// </summary> /// <remarks> /// Creates a new mutable child revision whose properties and attachments are initially identical /// to this one's, which you can modify and then save. /// </remarks> /// <returns> /// A new child <see cref="Couchbase.Lite.UnsavedRevision"/> whose properties and attachments /// are initially identical to this one. /// </returns> public UnsavedRevision CreateRevision() { var newRevision = new UnsavedRevision(Document, this); return(newRevision); }
protected abstract bool UpdateRevision (UnsavedRevision rev);