public void TestChangeNotification()
        {
            var changeNotifications = 0;

            EventHandler<DatabaseChangeEventArgs> handler
                = (sender, e) => changeNotifications++;

            database.Changed += handler;

            // create a document
            var documentProperties = new Dictionary<string, object>();
            documentProperties["foo"] = 1;
            documentProperties["bar"] = false;
            documentProperties["baz"] = "touch";
            
            var body = new Body(documentProperties);
            var rev1 = new RevisionInternal(body, database);
            
            var status = new Status();
            database.PutRevision(rev1, null, false, status);
            
            Assert.AreEqual(1, changeNotifications);

            // Analysis disable once DelegateSubtraction
            database.Changed -= handler;
        }
        public void TestChangeNotification()
        {
            var countDown = new CountdownEvent(1);
            EventHandler<DatabaseChangeEventArgs> handler
                = (sender, e) => countDown.Signal();

            database.Changed += handler;

            // create a document
            var documentProperties = new Dictionary<string, object>();
            documentProperties["foo"] = 1;
            documentProperties["bar"] = false;
            documentProperties["baz"] = "touch";
            
            var body = new Body(documentProperties);
            var rev1 = new RevisionInternal(body);

            database.PutRevision(rev1, null, false);
            Sleep(500);
            
            Assert.IsTrue(countDown.Wait(TimeSpan.FromSeconds(1)));

            // Analysis disable once DelegateSubtraction
            database.Changed -= handler;
        }
		internal ValidationContextImpl(Database database, RevisionInternal currentRevision
			, RevisionInternal newRev)
		{
			this.database = database;
			this.currentRevision = currentRevision;
			this.newRev = newRev;
		}
 public bool Run()
 {
     string[] bigObj = new string[this._enclosing.GetSizeOfDocument()];
     for (int i = 0; i < this._enclosing.GetSizeOfDocument(); i++)
     {
         bigObj[i] = Test10_DeleteDB._propertyValue;
     }
     for (int i_1 = 0; i_1 < this._enclosing.GetNumberOfDocuments(); i_1++)
     {
         //create a document
         IDictionary<string, object> props = new Dictionary<string, object>();
         props.Put("bigArray", bigObj);
         Body body = new Body(props);
         RevisionInternal rev1 = new RevisionInternal(body, this._enclosing.database);
         Status status = new Status();
         try
         {
             rev1 = this._enclosing.database.PutRevision(rev1, null, false, status);
         }
         catch (Exception t)
         {
             Log.E(Test10_DeleteDB.Tag, "Document create failed", t);
             return false;
         }
     }
     return true;
 }
		// Reproduces issue #167
		// https://github.com/couchbase/couchbase-lite-android/issues/167
		/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
		public virtual void TestLoadRevisionBody()
		{
			Document document = database.CreateDocument();
			IDictionary<string, object> properties = new Dictionary<string, object>();
			properties.Put("foo", "foo");
			properties.Put("bar", false);
			document.PutProperties(properties);
			NUnit.Framework.Assert.IsNotNull(document.GetCurrentRevision());
			bool deleted = false;
			RevisionInternal revisionInternal = new RevisionInternal(document.GetId(), document
				.GetCurrentRevisionId(), deleted, database);
			EnumSet<Database.TDContentOptions> contentOptions = EnumSet.Of(Database.TDContentOptions
				.TDIncludeAttachments, Database.TDContentOptions.TDBigAttachmentsFollow);
			database.LoadRevisionBody(revisionInternal, contentOptions);
			// now lets purge the document, and then try to load the revision body again
			NUnit.Framework.Assert.IsTrue(document.Purge());
			bool gotExpectedException = false;
			try
			{
				database.LoadRevisionBody(revisionInternal, contentOptions);
			}
			catch (CouchbaseLiteException e)
			{
				if (e.GetCBLStatus().GetCode() == Status.NotFound)
				{
					gotExpectedException = true;
				}
			}
			NUnit.Framework.Assert.IsTrue(gotExpectedException);
		}
 internal DocumentChange(RevisionInternal addedRevision, RevisionInternal winningRevision, bool isConflict, Uri sourceUrl)
 {
     AddedRevision = addedRevision;
     WinningRevision = winningRevision;
     IsConflict = isConflict;
     SourceUrl = sourceUrl;
 }
 internal DocumentChange(RevisionInternal addedRevision, RevisionInternal winningRevision
     , bool isConflict, Uri sourceUrl)
 {
     this.addedRevision = addedRevision;
     this.winningRevision = winningRevision;
     this.isConflict = isConflict;
     this.sourceUrl = sourceUrl;
 }
Beispiel #8
0
		/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
		private RevisionInternal PutDoc(Database db, IDictionary<string, object> props)
		{
			RevisionInternal rev = new RevisionInternal(props, db);
			Status status = new Status();
			rev = db.PutRevision(rev, null, false, status);
			NUnit.Framework.Assert.IsTrue(status.IsSuccessful());
			return rev;
		}
        public void TestForceInsertEmptyHistory()
        {
            var rev = new RevisionInternal("FakeDocId", "1-abcd".AsRevID(), false);
            var revProperties = new Dictionary<string, object>();
            revProperties.SetDocRevID(rev.DocID, rev.RevID);
            revProperties["message"] = "hi";
            rev.SetProperties(revProperties);

            IList<RevisionID> revHistory = null;
            database.ForceInsert(rev, revHistory, null);
        }
Beispiel #10
0
		/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
		public virtual void TestForceInsertEmptyHistory()
		{
			IList<string> revHistory = null;
			RevisionInternal rev = new RevisionInternal("FakeDocId", "1-tango", false, database
				);
			IDictionary<string, object> revProperties = new Dictionary<string, object>();
			revProperties.Put("_id", rev.GetDocId());
			revProperties.Put("_rev", rev.GetRevId());
			revProperties.Put("message", "hi");
			rev.SetProperties(revProperties);
			database.ForceInsert(rev, revHistory, null);
		}
        public void TestForceInsertEmptyHistory()
        {
            var rev = new RevisionInternal("FakeDocId", "1-abcd", false);
            var revProperties = new Dictionary<string, object>();
            revProperties.Put("_id", rev.GetDocId());
            revProperties.Put("_rev", rev.GetRevId());
            revProperties["message"] = "hi";
            rev.SetProperties(revProperties);

            IList<string> revHistory = null;
            database.ForceInsert(rev, revHistory, null);
        }
 internal QueryRow(string documentId, long sequence, object key, object value, RevisionInternal revision, IQueryRowStore storage)
 {
     // Don't initialize _database yet. I might be instantiated on a background thread (if the
     // query is async) which has a different CBLDatabase instance than the original caller.
     // Instead, the database property will be filled in when I'm added to a CBLQueryEnumerator.
     SourceDocumentId = documentId;
     SequenceNumber = sequence;
     _key = key;
     _value = value;
     _documentRevision = revision;
     _storage = storage;
 }
Beispiel #13
0
		/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
		public virtual void TestChangeNotification()
		{
			// add listener to database
            database.Changed += (sender, e) => changeNotifications++;
			// create a document
			IDictionary<string, object> documentProperties = new Dictionary<string, object>();
			documentProperties["foo"] = 1;
			documentProperties["bar"] = false;
			documentProperties["baz"] = "touch";
			Body body = new Body(documentProperties);
			RevisionInternal rev1 = new RevisionInternal(body, database);
			Status status = new Status();
			rev1 = database.PutRevision(rev1, null, false, status);
			NUnit.Framework.Assert.AreEqual(1, changeNotifications);
		}
Beispiel #14
0
        public override bool Equals(object o)
        {
            bool result = false;

            if (o is Couchbase.Lite.Internal.RevisionInternal)
            {
                Couchbase.Lite.Internal.RevisionInternal other = (Couchbase.Lite.Internal.RevisionInternal
                                                                  )o;
                if (docId.Equals(other.docId) && revId.Equals(other.revId))
                {
                    result = true;
                }
            }
            return(result);
        }
Beispiel #15
0
		/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
		public virtual void TestChangeNotification()
		{
			Database.ChangeListener changeListener = new _ChangeListener_16(this);
			// add listener to database
			database.AddChangeListener(changeListener);
			// create a document
			IDictionary<string, object> documentProperties = new Dictionary<string, object>();
			documentProperties.Put("foo", 1);
			documentProperties.Put("bar", false);
			documentProperties.Put("baz", "touch");
			Body body = new Body(documentProperties);
			RevisionInternal rev1 = new RevisionInternal(body, database);
			Status status = new Status();
			rev1 = database.PutRevision(rev1, null, false, status);
			NUnit.Framework.Assert.AreEqual(1, changeNotifications);
		}
 /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
 public virtual void TestLoadDBPerformance()
 {
     long startMillis = Runtime.CurrentTimeMillis();
     string[] bigObj = new string[GetSizeOfDocument()];
     for (int i = 0; i < GetSizeOfDocument(); i++)
     {
         bigObj[i] = _propertyValue;
     }
     for (int j = 0; j < GetNumberOfShutAndReloadCycles(); j++)
     {
         //Force close and reopen of manager and database to ensure cold
         //start before doc creation
         try
         {
             TearDown();
             manager = new Manager(new LiteTestContext(), Manager.DefaultOptions);
             database = manager.GetExistingDatabase(DefaultTestDb);
         }
         catch (Exception ex)
         {
             Log.E(Tag, "DB teardown", ex);
             Fail();
         }
         for (int k = 0; k < GetNumberOfDocuments(); k++)
         {
             //create a document
             IDictionary<string, object> props = new Dictionary<string, object>();
             props.Put("bigArray", bigObj);
             Body body = new Body(props);
             RevisionInternal rev1 = new RevisionInternal(body, database);
             Status status = new Status();
             try
             {
                 rev1 = database.PutRevision(rev1, null, false, status);
             }
             catch (Exception t)
             {
                 Log.E(Tag, "Document creation failed", t);
                 Fail();
             }
         }
     }
     Log.V("PerformanceStats", Tag + "," + Sharpen.Extensions.ValueOf(Runtime.CurrentTimeMillis
         () - startMillis).ToString() + "," + GetNumberOfDocuments() + "," + GetSizeOfDocument
         () + ",," + GetNumberOfShutAndReloadCycles());
 }
Beispiel #17
0
        public virtual Couchbase.Lite.Internal.RevisionInternal CopyWithDocID(string docId
                                                                              , string revId)
        {
            System.Diagnostics.Debug.Assert(((docId != null) && (revId != null)));
            System.Diagnostics.Debug.Assert(((this.docId == null) || (this.docId.Equals(docId
                                                                                        ))));
            Couchbase.Lite.Internal.RevisionInternal result = new Couchbase.Lite.Internal.RevisionInternal
                                                                  (docId, revId, deleted, database);
            IDictionary <string, object> unmodifiableProperties = GetProperties();
            IDictionary <string, object> properties             = new Dictionary <string, object>();

            if (unmodifiableProperties != null)
            {
                properties.PutAll(unmodifiableProperties);
            }
            properties.Put("_id", docId);
            properties.Put("_rev", revId);
            result.SetProperties(properties);
            return(result);
        }
 /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
 public virtual void TestCreateDocsUnoptimizedWayPerformance()
 {
     long startMillis = Runtime.CurrentTimeMillis();
     string[] bigObj = new string[GetSizeOfDocument()];
     for (int i = 0; i < GetSizeOfDocument(); i++)
     {
         bigObj[i] = _propertyValue;
     }
     for (int i_1 = 0; i_1 < GetNumberOfDocuments(); i_1++)
     {
         //create a document
         IDictionary<string, object> props = new Dictionary<string, object>();
         props.Put("bigArray", bigObj);
         Body body = new Body(props);
         RevisionInternal rev1 = new RevisionInternal(body, database);
         Status status = new Status();
         rev1 = database.PutRevision(rev1, null, false, status);
     }
     Log.V("PerformanceStats", Tag + "," + Sharpen.Extensions.ValueOf(Runtime.CurrentTimeMillis
         () - startMillis).ToString() + "," + GetNumberOfDocuments() + "," + GetSizeOfDocument
         ());
 }
        public static ICouchbaseResponseState RevsDiff(ICouchbaseListenerContext context)
        {
            // Collect all of the input doc/revision IDs as CBL_Revisions:
            var revs = new RevisionList();
            var body = context.BodyAs<Dictionary<string, object>>();
            if (body == null) {
                return context.CreateResponse(StatusCode.BadJson).AsDefaultState();
            }

            foreach (var docPair in body) {
                var revIDs = docPair.Value.AsList<string>();
                if (revIDs == null) {
                    return context.CreateResponse(StatusCode.BadParam).AsDefaultState();
                }

                foreach (var revID in revIDs) {
                    var rev = new RevisionInternal(docPair.Key, revID.AsRevID(), false);
                    revs.Add(rev);
                }
            }

            return PerformLogicWithDatabase(context, true, db =>
            {
                var response = context.CreateResponse();
                // Look them up, removing the existing ones from revs:
                db.Storage.FindMissingRevisions(revs);

                // Return the missing revs in a somewhat different format:
                IDictionary<string, object> diffs = new Dictionary<string, object>();
                foreach(var rev in revs) {
                    var docId = rev.DocID;
                    IList<RevisionID> missingRevs = null;
                    if(!diffs.ContainsKey(docId)) {
                        missingRevs = new List<RevisionID>();
                        diffs[docId] = new Dictionary<string, IList<RevisionID>> { { "missing", missingRevs } };
                    } else {
                        missingRevs = ((Dictionary<string, IList<RevisionID>>)diffs[docId])["missing"];
                    }

                    missingRevs.Add(rev.RevID);
                }

                // Add the possible ancestors for each missing revision:
                foreach(var docPair in diffs) {
                    IDictionary<string, IList<RevisionID>> docInfo = (IDictionary<string, IList<RevisionID>>)docPair.Value;
                    int maxGen = 0;
                    RevisionID maxRevID = null;
                    foreach(var revId in docInfo["missing"]) {
                        if(revId.Generation > maxGen) {
                            maxGen = revId.Generation;
                            maxRevID = revId;
                        }
                    }

                    var rev = new RevisionInternal(docPair.Key, maxRevID, false);
                    var ancestors = db.Storage.GetPossibleAncestors(rev, 0, ValueTypePtr<bool>.NULL)?.ToList();
                    if(ancestors != null && ancestors.Count > 0) {
                        docInfo["possible_ancestors"] = ancestors;
                    }
                }

                response.JsonBody = new Body(diffs);
                return response;
            }).AsDefaultState();

        }
        /// <summary>
        /// Queries the specified view using the specified options
        /// </summary>
        /// <returns>The HTTP response containing the results of the query</returns>
        /// <param name="context">The request context</param>
        /// <param name="db">The database to run the query in</param>
        /// <param name="view">The view to query</param>
        /// <param name="options">The options to apply to the query</param>
        public static CouchbaseLiteResponse QueryView(ICouchbaseListenerContext context, Database db, View view, QueryOptions options)
        {
            var result = view.QueryWithOptions(options);

            object updateSeq = options.UpdateSeq ? (object)view.LastSequenceIndexed : null;
            var mappedResult = new List<object>();
            foreach (var row in result) {
                row.Database = db;
                var dict = row.AsJSONDictionary();
                if (context.ContentOptions != DocumentContentOptions.None) {
                    var doc = dict.Get("doc").AsDictionary<string, object>();
                    if (doc != null) {
                        // Add content options:
                        RevisionInternal rev = new RevisionInternal(doc);
                        var status = new Status();
                        rev = DocumentMethods.ApplyOptions(context.ContentOptions, rev, context, db, status);
                        if (rev != null) {
                            dict["doc"] = rev.GetProperties();
                        }
                    }
                }

                mappedResult.Add(dict);
            }

            var body = new Body(new NonNullDictionary<string, object> {
                { "rows", mappedResult },
                { "total_rows", view.TotalRows },
                { "offset", options.Skip },
                { "update_seq", updateSeq }
            });

            var retVal = context.CreateResponse();
            retVal.JsonBody = body;
            return retVal;
        }
 /// <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.Sequence = rev.Sequence;
             rev = rev2;
         }
     }
     return new NonNullDictionary<string, object> {
         { "seq", rev.Sequence },
         { "id", rev.DocID },
         { "changes", new List<object> { 
                 new Dictionary<string, object> { 
                     { "rev", rev.RevID } 
                 } 
             } 
         },
         { "deleted", rev.Deleted ? (object)true : null },
         { "doc", responseState.ChangesIncludeDocs ? rev.GetProperties() : null }
     };
 }
        /// <summary>
        /// Create and update multiple documents at the same time within a single request.
        /// </summary>
        /// <returns>The response state for further HTTP processing</returns>
        /// <param name="context">The context of the Couchbase Lite HTTP request</param>
        /// <remarks>
        /// http://docs.couchdb.org/en/latest/api/database/bulk-api.html#post--db-_bulk_docs
        /// </remarks>
        public static ICouchbaseResponseState ProcessDocumentChangeOperations(ICouchbaseListenerContext context)
        {
            return PerformLogicWithDatabase(context, true, db =>
            {
                var postBody = context.BodyAs<Dictionary<string, object>>();
                if(postBody == null) {
                    return context.CreateResponse(StatusCode.BadJson);
                }
                    
                if(!postBody.ContainsKey("docs")) {
                    return context.CreateResponse(StatusCode.BadParam);
                }
                var docs = postBody["docs"].AsList<IDictionary<string, object>>();

                bool allOrNothing;
                postBody.TryGetValue<bool>("all_or_nothing", out allOrNothing);

                bool newEdits;
                postBody.TryGetValue<bool>("new_edits", out newEdits);

                var response = context.CreateResponse();
                StatusCode status = StatusCode.Ok;
                bool success = db.RunInTransaction(() => {
                    List<IDictionary<string, object>> results = new List<IDictionary<string, object>>(docs.Count);
                    var castContext = context as ICouchbaseListenerContext2;
                    var source = castContext != null && !castContext.IsLoopbackRequest ? castContext.Sender : null;
                    foreach(var doc in docs) {
                        string docId = doc.CblID();
                        RevisionInternal rev = null;
                        Body body = new Body(doc);

                        if(!newEdits) {
                            if(!RevisionInternal.IsValid(body)) {
                                status = StatusCode.BadParam;
                            } else {
                                rev = new RevisionInternal(body);
                                var history = Database.ParseCouchDBRevisionHistory(doc);
                                try {
                                    db.ForceInsert(rev, history, source);
                                } catch(CouchbaseLiteException e) {
                                    status = e.Code;
                                }
                            } 
                        } else {
                            status = DocumentMethods.UpdateDocument(context, db, docId, body, false, allOrNothing, out rev);
                        }

                        IDictionary<string, object> result = null;
                        if((int)status < 300) {
                            Debug.Assert(rev != null && rev.RevID != null);
                            if(newEdits) {
                                result = new Dictionary<string, object>
                                {
                                    { "id", rev.DocID },
                                    { "rev", rev.RevID },
                                    { "status", (int)status }
                                };
                            }
                        } else if((int)status >= 500) {
                            return false; // abort the whole thing if something goes badly wrong
                        } else if(allOrNothing) {
                            return false; // all_or_nothing backs out if there's any error
                        } else {
                            var info = Status.ToHttpStatus(status);
                            result = new Dictionary<string, object>
                            {
                                { "id", docId },
                                { "error", info.Item2 },
                                { "status", info.Item1 }
                            };
                        }

                        if(result != null) {
                            results.Add(result);
                        }
                    }

                    response.JsonBody = new Body(results.Cast<object>().ToList());
                    return true;
                });

                if(!success) {
                    response.InternalStatus = status;
                }

                return response;
            }).AsDefaultState();
        }
        internal bool RunFilter(FilterDelegate filter, IDictionary<string, object> filterParams, RevisionInternal rev)
        {
            if (filter == null) {
                return true;
            }

            var publicRev = new SavedRevision(this, rev);
            return filter(publicRev, filterParams);
        }
        private void SetupRevisionBodyTransformationFunction()
        {
            var xformer = TransformationFunction;
            if (xformer != null)
            {
                RevisionBodyTransformationFunction = (rev) =>
                {
                    var properties = rev.GetProperties();

                    var xformedProperties = xformer(properties);
                    if (xformedProperties == null) 
                    {
                        return null;
                    }
                    if (xformedProperties != properties) {
                        Debug.Assert (xformedProperties != null);
                        Debug.Assert (xformedProperties ["_id"].Equals (properties ["_id"]));
                        Debug.Assert (xformedProperties ["_rev"].Equals (properties ["_rev"]));

                        var nuRev = new RevisionInternal (rev.GetProperties ());
                        nuRev.SetProperties (xformedProperties);
                        return nuRev;
                    }
                    return rev;
                };
            }
        }
        /// <summary>VALIDATION</summary>
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
        internal Status ValidateRevision(RevisionInternal newRev, RevisionInternal oldRev, String parentRevId)
        {
            var validations = Shared.GetValues("validation", Name);
            if (validations == null || validations.Count == 0) {
                return new Status(StatusCode.Ok);
            }

            var publicRev = new SavedRevision(this, newRev, parentRevId);
            var context = new ValidationContext(this, oldRev, newRev);
            Status status = new Status(StatusCode.Ok);
            foreach (var validationName in validations.Keys)
            {
                var validation = GetValidation(validationName);
                try {
                    validation(publicRev, context);
                } catch(Exception e) {
                    Log.E(TAG, String.Format("Validation block '{0}'", validationName), e);
                    status.Code = StatusCode.Exception;
                    break;
                }
                    
                if (context.RejectMessage != null) {
                    Log.D(TAG, "Failed update of {0}: {1}:{2} Old doc = {3}{2} New doc = {4}", oldRev, context.RejectMessage,
                            Environment.NewLine, oldRev == null ? null : oldRev.GetProperties(), newRev.GetProperties());
                    status.Code = StatusCode.Forbidden;
                    break;
                }
            }

            return status;
        }
        /// <summary>Updates or deletes an attachment, creating a new document revision in the process.
        ///     </summary>
        /// <remarks>
        /// Updates or deletes an attachment, creating a new document revision in the process.
        /// Used by the PUT / DELETE methods called on attachment URLs.
        /// </remarks>
        /// <exclude></exclude>
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
        internal RevisionInternal UpdateAttachment(string filename, BlobStoreWriter body, string contentType, AttachmentEncoding encoding, string docID, string oldRevID)
        {
            if(StringEx.IsNullOrWhiteSpace(filename) || (body != null && contentType == null) || 
                (oldRevID != null && docID == null) || (body != null && docID == null)) {
                throw new CouchbaseLiteException(StatusCode.BadAttachment);
            }

            var oldRev = new RevisionInternal(docID, oldRevID, false);
            if (oldRevID != null) {
                // Load existing revision if this is a replacement:
                try {
                    oldRev = LoadRevisionBody(oldRev);
                } catch (CouchbaseLiteException e) {
                    if (e.Code == StatusCode.NotFound && GetDocument(docID, null, false) != null) {
                        throw new CouchbaseLiteException(StatusCode.Conflict);
                    }

                    throw;
                }
            } else {
                // If this creates a new doc, it needs a body:
                oldRev.SetBody(new Body(new Dictionary<string, object>()));
            }

            // Update the _attachments dictionary:
            var attachments = oldRev.GetProperties().Get("_attachments").AsDictionary<string, object>();
            if (attachments == null) {
                attachments = new Dictionary<string, object>();
            }

            if (body != null) {
                var key = body.GetBlobKey();
                string digest = key.Base64Digest();
                RememberAttachmentWriter(body);
                string encodingName = (encoding == AttachmentEncoding.GZIP) ? "gzip" : null;
                attachments[filename] = new NonNullDictionary<string, object> {
                    { "digest", digest },
                    { "length", body.GetLength() },
                    { "follows", true },
                    { "content_type", contentType },
                    { "encoding", encodingName }
                };
            } else {
                if (oldRevID != null && attachments.Get(filename) == null) {
                    throw new CouchbaseLiteException(StatusCode.AttachmentNotFound);
                }

                attachments.Remove(filename);
            }

            var properties = oldRev.GetProperties();
            properties["_attachments"] = attachments;
            oldRev.SetProperties(properties);

            Status status = new Status();
            var newRev = PutRevision(oldRev, oldRevID, false, status);
            if (status.IsError) {
                throw new CouchbaseLiteException(status.Code);
            }

            return newRev;
        }
        //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;
        }
        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;
        }
 internal void AddToInbox(RevisionInternal rev)
 {
     Debug.Assert(IsRunning);
     Batcher.QueueObject(rev);
 }
 /// <summary>
 /// Sets the contents of the local <see cref="Couchbase.Lite.Document" /> with the given id.  If <param name="properties"/> is null, the 
 /// <see cref="Couchbase.Lite.Document" /> is deleted.
 /// </summary>
 /// <param name="id">The id of the local document whos contents to set.</param>
 /// <param name="properties">The contents to set for the local document.</param>
 /// <exception cref="Couchbase.Lite.CouchbaseLiteException">Thrown if an issue occurs 
 /// while setting the contents of the local document.</exception>
 public bool PutLocalDocument(string id, IDictionary<string, object> properties) 
 { 
     id = MakeLocalDocumentId(id);
     var rev = new RevisionInternal(id, null, properties == null);
     if (properties != null) {
         rev.SetProperties(properties);
     }
         
     bool ok = Storage.PutLocalRevision(rev, null, false) != null;
     return ok;
 }
		/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
		public virtual void TestValidations()
		{
            Database.ValidateDelegate validator = (Revision newRevision, ValidationContext context)=>
            {
                NUnit.Framework.Assert.IsNotNull(newRevision);
                NUnit.Framework.Assert.IsNotNull(context);
                NUnit.Framework.Assert.IsTrue(newRevision.Properties != null || newRevision.
                    IsDeletion);
                this._enclosing.validationCalled = true;
                bool hoopy = newRevision.IsDeletion || (newRevision.Properties.Get("towel"
                ) != null);
                Log.V(ValidationsTest.Tag, string.Format("--- Validating %s --> %b", newRevision.
                    Properties, hoopy));
                if (!hoopy)
                {
                    context.Reject("Where's your towel?");
                }
                return hoopy;
            };
			database.SetValidation("hoopy", validator);
			// POST a valid new document:
			IDictionary<string, object> props = new Dictionary<string, object>();
			props["name"] = "Zaphod Beeblebrox";
			props["towel"] = "velvet";
			RevisionInternal rev = new RevisionInternal(props, database);
			Status status = new Status();
			validationCalled = false;
			rev = database.PutRevision(rev, null, false, status);
			NUnit.Framework.Assert.IsTrue(validationCalled);
            NUnit.Framework.Assert.AreEqual(StatusCode.Created, status.GetCode());
			// PUT a valid update:
			props["head_count"] = 3;
			rev.SetProperties(props);
			validationCalled = false;
			rev = database.PutRevision(rev, rev.GetRevId(), false, status);
			NUnit.Framework.Assert.IsTrue(validationCalled);
            NUnit.Framework.Assert.AreEqual(StatusCode.Created, status.GetCode());
			// PUT an invalid update:
			Sharpen.Collections.Remove(props, "towel");
			rev.SetProperties(props);
			validationCalled = false;
			bool gotExpectedError = false;
			try
			{
				rev = database.PutRevision(rev, rev.GetRevId(), false, status);
			}
			catch (CouchbaseLiteException e)
			{
                gotExpectedError = (e.GetCBLStatus().GetCode() == StatusCode.Forbidden);
			}
			NUnit.Framework.Assert.IsTrue(validationCalled);
			NUnit.Framework.Assert.IsTrue(gotExpectedError);
			// POST an invalid new document:
			props = new Dictionary<string, object>();
			props["name"] = "Vogon";
			props["poetry"] = true;
			rev = new RevisionInternal(props, database);
			validationCalled = false;
			gotExpectedError = false;
			try
			{
				rev = database.PutRevision(rev, null, false, status);
			}
			catch (CouchbaseLiteException e)
			{
                gotExpectedError = (e.GetCBLStatus().GetCode() == StatusCode.Forbidden);
			}
			NUnit.Framework.Assert.IsTrue(validationCalled);
			NUnit.Framework.Assert.IsTrue(gotExpectedError);
			// PUT a valid new document with an ID:
			props = new Dictionary<string, object>();
			props["_id"] = "ford";
			props["name"] = "Ford Prefect";
			props["towel"] = "terrycloth";
			rev = new RevisionInternal(props, database);
			validationCalled = false;
			rev = database.PutRevision(rev, null, false, status);
			NUnit.Framework.Assert.IsTrue(validationCalled);
			NUnit.Framework.Assert.AreEqual("ford", rev.GetDocId());
			// DELETE a document:
			rev = new RevisionInternal(rev.GetDocId(), rev.GetRevId(), true, database);
			NUnit.Framework.Assert.IsTrue(rev.IsDeleted());
			validationCalled = false;
			rev = database.PutRevision(rev, rev.GetRevId(), false, status);
			NUnit.Framework.Assert.IsTrue(validationCalled);
			// PUT an invalid new document:
			props = new Dictionary<string, object>();
			props["_id"] = "petunias";
			props["name"] = "Pot of Petunias";
			rev = new RevisionInternal(props, database);
			validationCalled = false;
			gotExpectedError = false;
			try
			{
				rev = database.PutRevision(rev, null, false, status);
			}
			catch (CouchbaseLiteException e)
			{
                gotExpectedError = (e.GetCBLStatus().GetCode() == StatusCode.Forbidden);
			}
			NUnit.Framework.Assert.IsTrue(validationCalled);
			NUnit.Framework.Assert.IsTrue(gotExpectedError);
		}
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException">When attempting to add an invalid revision</exception>
        internal void ForceInsert(RevisionInternal inRev, IList<string> revHistory, Uri source)
        {
            if (revHistory == null) {
                revHistory = new List<string>(0);
            }

            var rev = inRev.CopyWithDocID(inRev.GetDocId(), inRev.GetRevId());
            rev.SetSequence(0);
            string revID = rev.GetRevId();
            if (!IsValidDocumentId(rev.GetDocId()) || revID == null) {
                throw new CouchbaseLiteException(StatusCode.BadId);
            }

            if (revHistory.Count == 0) {
                revHistory.Add(revID);
            } else if (revID != revHistory[0]) {
                throw new CouchbaseLiteException(StatusCode.BadId);
            }

            if (inRev.GetAttachments() != null) {
                var updatedRev = inRev.CopyWithDocID(inRev.GetDocId(), inRev.GetRevId());
                string prevRevID = revHistory.Count >= 2 ? revHistory[1] : null;
                Status status = new Status();
                if (!ProcessAttachmentsForRevision(updatedRev, prevRevID, status)) {
                    throw new CouchbaseLiteException(status.Code);
                }

                inRev = updatedRev;
            }

            StoreValidation validationBlock = null;
            if (Shared != null && Shared.HasValues("validation", Name)) {
                validationBlock = ValidateRevision;
            }

            var insertStatus = Storage.ForceInsert(inRev, revHistory, validationBlock, source);
            if(insertStatus.IsError) {
                throw new CouchbaseLiteException(insertStatus.Code);
            }
        }