/** The revision ID of the document this row was mapped from. */ @InterfaceAudience.Public public String getDocumentRevisionId() { // Get the revision id from either the embedded document contents, // or the '_rev' or 'rev' value key: String rev = null; if (documentRevision != null) rev = documentRevision.getRevID(); if (rev == null) { if (value instanceof Map) { Map<String, Object> mapValue = (Map<String, Object>) value; rev = (String) mapValue.get("_rev"); if (rev == null) { rev = (String) mapValue.get("rev"); } } } return rev; }
/** - (void) processInbox: (CBL_RevisionList*)changes in CBLRestPusher.m */ @Override @InterfaceAudience.Private protected void processInbox(final RevisionList changes) { Log.v(Log.TAG_SYNC, "processInbox() changes=" + changes.size()); // Generate a set of doc/rev IDs in the JSON format that _revs_diff wants: // <http://wiki.apache.org/couchdb/HttpPostRevsDiff> Map<String, List<String>> diffs = new HashMap<String, List<String>>(); for (RevisionInternal rev : changes) { String docID = rev.getDocID(); List<String> revs = diffs.get(docID); if (revs == null) { revs = new ArrayList<String>(); diffs.put(docID, revs); } revs.add(rev.getRevID()); addPending(rev); } // Call _revs_diff on the target db: Log.v(Log.TAG_SYNC, "%s: posting to /_revs_diff", this); CustomFuture future = sendAsyncRequest( "POST", "/_revs_diff", diffs, new RemoteRequestCompletionBlock() { @Override public void onCompletion(HttpResponse httpResponse, Object response, Throwable e) { Log.v(Log.TAG_SYNC, "%s: got /_revs_diff response", this); Map<String, Object> results = (Map<String, Object>) response; if (e != null) { setError(e); } else { if (results.size() != 0) { // 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: List<Object> docsToSend = new ArrayList<Object>(); RevisionList revsToSend = new RevisionList(); long bufferedSize = 0; for (RevisionInternal rev : changes) { // Is this revision in the server's 'missing' list? Map<String, Object> properties = null; Map<String, Object> revResults = (Map<String, Object>) results.get(rev.getDocID()); if (revResults == null) { removePending(rev); continue; } List<String> revs = (List<String>) revResults.get("missing"); if (revs == null || !revs.contains(rev.getRevID())) { removePending(rev); continue; } // NOTE: force to load body by Database.loadRevisionBody() // In SQLiteStore.loadRevisionBody() does not load data from database // if sequence != 0 && body != null rev.setSequence(0); rev.setBody(null); RevisionInternal loadedRev; try { loadedRev = db.loadRevisionBody(rev); } catch (CouchbaseLiteException e1) { Log.w( Log.TAG_SYNC, "%s Couldn't get local contents of %s", rev, PusherInternal.this); continue; } RevisionInternal populatedRev = transformRevision(loadedRev); loadedRev = null; List<String> possibleAncestors = (List<String>) revResults.get("possible_ancestors"); properties = new HashMap<String, Object>(populatedRev.getProperties()); Map<String, Object> revisions = db.getRevisionHistoryDictStartingFromAnyAncestor( populatedRev, possibleAncestors); properties.put("_revisions", revisions); populatedRev.setProperties(properties); // Strip any attachments already known to the target db: if (properties.containsKey("_attachments")) { // Look for the latest common ancestor and stub out older attachments: int minRevPos = findCommonAncestor(populatedRev, possibleAncestors); Status status = new Status(Status.OK); if (!db.expandAttachments( populatedRev, minRevPos + 1, !dontSendMultipart, false, status)) { Log.w( Log.TAG_SYNC, "%s: Couldn't expand attachments of %s", this, populatedRev); continue; } properties = populatedRev.getProperties(); if (!dontSendMultipart && uploadMultipartRevision(populatedRev)) { continue; } } if (properties == null || !properties.containsKey("_id")) { throw new IllegalStateException("properties must contain a document _id"); } revsToSend.add(rev); docsToSend.add(properties); bufferedSize += JSONUtils.estimate(properties); if (bufferedSize > kMaxBulkDocsObjectSize) { uploadBulkDocs(docsToSend, revsToSend); docsToSend = new ArrayList<Object>(); revsToSend = new RevisionList(); bufferedSize = 0; } } // Post the revisions to the destination: uploadBulkDocs(docsToSend, revsToSend); } else { // None of the revisions are new to the remote for (RevisionInternal revisionInternal : changes) { removePending(revisionInternal); } } } } }); future.setQueue(pendingFutures); pendingFutures.add(future); pauseOrResume(); }
/** * Fetches the contents of a revision from the remote db, including its parent revision ID. The * contents are stored into rev.properties. */ @InterfaceAudience.Private public void pullRemoteRevision(final RevisionInternal rev) { Log.d(Log.TAG_SYNC, "%s: pullRemoteRevision with rev: %s", this, rev); ++httpConnectionCount; // Construct a query. We want the revision history, and the bodies of attachments that have // been added since the latest revisions we have locally. // See: http://wiki.apache.org/couchdb/HTTP_Document_API#Getting_Attachments_With_a_Document StringBuilder path = new StringBuilder("/"); path.append(encodeDocumentId(rev.getDocID())); path.append("?rev=").append(URIUtils.encode(rev.getRevID())); path.append("&revs=true&attachments=true"); // If the document has attachments, add an 'atts_since' param with a list of // already-known revisions, so the server can skip sending the bodies of any // attachments we already have locally: AtomicBoolean hasAttachment = new AtomicBoolean(false); List<String> knownRevs = db.getPossibleAncestorRevisionIDs( rev, PullerInternal.MAX_NUMBER_OF_ATTS_SINCE, hasAttachment); if (hasAttachment.get() && knownRevs != null && knownRevs.size() > 0) { path.append("&atts_since="); path.append(joinQuotedEscaped(knownRevs)); } // create a final version of this variable for the log statement inside // FIXME find a way to avoid this final String pathInside = path.toString(); CustomFuture future = sendAsyncMultipartDownloaderRequest( "GET", pathInside, null, db, new RemoteRequestCompletionBlock() { @Override public void onCompletion(HttpResponse httpResponse, Object result, Throwable e) { if (e != null) { Log.e(Log.TAG_SYNC, "Error pulling remote revision", e); revisionFailed(rev, e); } else { Map<String, Object> properties = (Map<String, Object>) result; PulledRevision gotRev = new PulledRevision(properties); gotRev.setSequence(rev.getSequence()); Log.d( Log.TAG_SYNC, "%s: pullRemoteRevision add rev: %s to batcher: %s", PullerInternal.this, gotRev, downloadsToInsert); if (gotRev.getBody() != null) gotRev.getBody().compact(); // Add to batcher ... eventually it will be fed to -insertRevisions:. downloadsToInsert.queueObject(gotRev); } // Note that we've finished this task: --httpConnectionCount; // Start another task if there are still revisions waiting to be pulled: pullRemoteRevisions(); } }); future.setQueue(pendingFutures); pendingFutures.add(future); }