public void TestMakeRevisionHistoryDict()
        {
            var revs = new List <RevisionID>();

            revs.Add("4-jkl".AsRevID());
            revs.Add("3-ghi".AsRevID());
            revs.Add("2-def".AsRevID());

            var expectedSuffixes = new List <string>();

            expectedSuffixes.Add("jkl");
            expectedSuffixes.Add("ghi");
            expectedSuffixes.Add("def");

            var expectedHistoryDict = new Dictionary <string, object>();

            expectedHistoryDict["start"] = 4;
            expectedHistoryDict["ids"]   = expectedSuffixes;

            var historyDict = TreeRevisionID.MakeRevisionHistoryDict(revs);

            Assert.AreEqual(expectedHistoryDict, historyDict);

            revs = new List <RevisionID>();
            revs.Add("4-jkl".AsRevID());
            revs.Add("2-def".AsRevID());

            expectedSuffixes = new List <string>();
            expectedSuffixes.Add("4-jkl");
            expectedSuffixes.Add("2-def");

            expectedHistoryDict        = new Dictionary <string, object>();
            expectedHistoryDict["ids"] = expectedSuffixes;
            historyDict = TreeRevisionID.MakeRevisionHistoryDict(revs);
            Assert.AreEqual(expectedHistoryDict, historyDict);

            revs = new List <RevisionID>();
            revs.Add("12345".AsRevID());
            revs.Add("6789".AsRevID());

            expectedSuffixes = new List <string>();
            expectedSuffixes.Add("12345");
            expectedSuffixes.Add("6789");

            expectedHistoryDict        = new Dictionary <string, object>();
            expectedHistoryDict["ids"] = expectedSuffixes;
            historyDict = TreeRevisionID.MakeRevisionHistoryDict(revs);

            Assert.AreEqual(expectedHistoryDict, historyDict);
        }
        // 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)
                 | DocumentContentOptions.IncludeExpiration) != 0)
            {
                var dst = rev.GetProperties() ?? new Dictionary <string, object>();
                if (options.HasFlag(DocumentContentOptions.IncludeLocalSeq))
                {
                    dst["_local_seq"] = rev.Sequence;
                }

                if (options.HasFlag(DocumentContentOptions.IncludeRevs))
                {
                    var revs = db.GetRevisionHistory(rev, null);
                    dst["_revisions"] = TreeRevisionID.MakeRevisionHistoryDict(revs);
                }

                if (options.HasFlag(DocumentContentOptions.IncludeRevsInfo))
                {
                    dst["_revs_info"] = db.GetRevisionHistory(rev, null).Select(x =>
                    {
                        string status = "available";
                        var ancestor  = db.GetDocument(rev.DocID, x, true);
                        if (ancestor.Deleted)
                        {
                            status = "deleted";
                        }
                        else if (ancestor.Missing)
                        {
                            status = "missing";
                        }

                        return(new Dictionary <string, object> {
                            { "rev", x.ToString() },
                            { "status", status }
                        });
                    });
                }

                if (options.HasFlag(DocumentContentOptions.IncludeConflicts))
                {
                    RevisionList revs = db.Storage.GetAllDocumentRevisions(rev.DocID, true, false);
                    if (revs.Count > 1)
                    {
                        dst["_conflicts"] = from r in revs
                                            where !r.Equals(rev) && !r.Deleted
                                            select r.RevID.ToString();
                    }
                }

                if (options.HasFlag(DocumentContentOptions.IncludeExpiration))
                {
                    var expirationTime = db.Storage?.GetDocumentExpiration(rev.DocID);
                    if (expirationTime.HasValue)
                    {
                        dst["_exp"] = expirationTime;
                    }
                }

                RevisionInternal nuRev = new RevisionInternal(dst);
                if (options.HasFlag(DocumentContentOptions.IncludeAttachments))
                {
                    bool attEncodingInfo = context != null && context.GetQueryParam <bool>("att_encoding_info", bool.TryParse, false);
                    db.ExpandAttachments(nuRev, 0, false, !attEncodingInfo);
                }

                rev = nuRev;
            }

            return(rev);
        }
Example #3
0
        private void UploadChanges(IList <RevisionInternal> changes, IDictionary <string, object> revsDiffResults)
        {
            // Go through the list of local changes again, selecting the ones the destination server
            // said were missing and mapping them to a JSON dictionary in the form _bulk_docs wants:
            var docsToSend = new List <object> ();
            var revsToSend = new RevisionList();
            IDictionary <string, object> revResults = null;

            foreach (var rev in changes)
            {
                // Is this revision in the server's 'missing' list?
                if (revsDiffResults != null)
                {
                    revResults = revsDiffResults.Get(rev.DocID).AsDictionary <string, object>();
                    if (revResults == null)
                    {
                        continue;
                    }

                    var revs = revResults.Get("missing").AsList <string>();
                    if (revs == null || !revs.Any(id => id.Equals(rev.RevID.ToString())))
                    {
                        RemovePending(rev);
                        continue;
                    }
                }

                IDictionary <string, object> properties = null;
                RevisionInternal             loadedRev;
                try {
                    loadedRev = LocalDatabase.LoadRevisionBody(rev);
                    if (loadedRev == null)
                    {
                        throw Misc.CreateExceptionAndLog(Log.To.Sync, StatusCode.NotFound, TAG,
                                                         "Unable to load revision body");
                    }

                    properties = new Dictionary <string, object>(rev.GetProperties());
                } catch (Exception e1) {
                    Log.To.Sync.E(TAG, String.Format("Couldn't get local contents of {0}, marking revision failed",
                                                     rev), e1);
                    RevisionFailed();
                    continue;
                }

                var populatedRev = TransformRevision(loadedRev);
                IList <RevisionID> possibleAncestors = null;
                if (revResults != null && revResults.ContainsKey("possible_ancestors"))
                {
                    possibleAncestors = revResults["possible_ancestors"].AsList <RevisionID>();
                }

                properties = new Dictionary <string, object>(populatedRev.GetProperties());

                try {
                    var history = LocalDatabase.GetRevisionHistory(populatedRev, possibleAncestors);
                    if (history == null)
                    {
                        throw Misc.CreateExceptionAndLog(Log.To.Sync, StatusCode.DbError, TAG,
                                                         "Unable to load revision history");
                    }

                    properties["_revisions"] = TreeRevisionID.MakeRevisionHistoryDict(history);
                } catch (Exception e1) {
                    Log.To.Sync.E(TAG, "Error getting revision history, marking revision failed", e1);
                    RevisionFailed();
                    continue;
                }

                populatedRev.SetProperties(properties);
                if (properties.GetCast <bool>("_removed"))
                {
                    RemovePending(rev);
                    continue;
                }

                // Strip any attachments already known to the target db:
                if (properties.ContainsKey("_attachments"))
                {
                    // Look for the latest common ancestor and stuf out older attachments:
                    var minRevPos = FindCommonAncestor(populatedRev, possibleAncestors);
                    try {
                        LocalDatabase.ExpandAttachments(populatedRev, minRevPos + 1, !_dontSendMultipart, false);
                    } catch (Exception ex) {
                        Log.To.Sync.E(TAG, "Error expanding attachments, marking revision failed", ex);
                        RevisionFailed();
                        continue;
                    }

                    properties = populatedRev.GetProperties();
                    if (!_dontSendMultipart && UploadMultipartRevision(populatedRev))
                    {
                        continue;
                    }
                }

                if (properties == null || !properties.ContainsKey("_id"))
                {
                    throw Misc.CreateExceptionAndLog(Log.To.Sync, StatusCode.BadParam, TAG,
                                                     "properties must contain a document _id");
                }

                // Add the _revisions list:
                revsToSend.Add(rev);

                //now add it to the docs to send
                docsToSend.Add(properties);
            }

            UploadBulkDocs(docsToSend, revsToSend);
        }