| import { clone, flatten } from 'pouchdb-utils'; |
| |
| function isGenOne(rev) { |
| return /^1-/.test(rev); |
| } |
| |
| function fileHasChanged(localDoc, remoteDoc, filename) { |
| return !localDoc._attachments || |
| !localDoc._attachments[filename] || |
| localDoc._attachments[filename].digest !== remoteDoc._attachments[filename].digest; |
| } |
| |
| function getDocAttachments(db, doc) { |
| var filenames = Object.keys(doc._attachments); |
| return Promise.all(filenames.map(function (filename) { |
| return db.getAttachment(doc._id, filename, {rev: doc._rev}); |
| })); |
| } |
| |
| function getDocAttachmentsFromTargetOrSource(target, src, doc) { |
| var doCheckForLocalAttachments = src.type() === 'http' && target.type() !== 'http'; |
| var filenames = Object.keys(doc._attachments); |
| |
| if (!doCheckForLocalAttachments) { |
| return getDocAttachments(src, doc); |
| } |
| |
| return target.get(doc._id).then(function (localDoc) { |
| return Promise.all(filenames.map(function (filename) { |
| if (fileHasChanged(localDoc, doc, filename)) { |
| return src.getAttachment(doc._id, filename); |
| } |
| |
| return target.getAttachment(localDoc._id, filename); |
| })); |
| }).catch(function (error) { |
| /* istanbul ignore if */ |
| if (error.status !== 404) { |
| throw error; |
| } |
| |
| return getDocAttachments(src, doc); |
| }); |
| } |
| |
| function createBulkGetOpts(diffs) { |
| var requests = []; |
| Object.keys(diffs).forEach(function (id) { |
| var missingRevs = diffs[id].missing; |
| missingRevs.forEach(function (missingRev) { |
| requests.push({ |
| id: id, |
| rev: missingRev |
| }); |
| }); |
| }); |
| |
| return { |
| docs: requests, |
| revs: true, |
| latest: true |
| }; |
| } |
| |
| // |
| // Fetch all the documents from the src as described in the "diffs", |
| // which is a mapping of docs IDs to revisions. If the state ever |
| // changes to "cancelled", then the returned promise will be rejected. |
| // Else it will be resolved with a list of fetched documents. |
| // |
| function getDocs(src, target, diffs, state) { |
| diffs = clone(diffs); // we do not need to modify this |
| |
| var resultDocs = [], |
| ok = true; |
| |
| function getAllDocs() { |
| |
| var bulkGetOpts = createBulkGetOpts(diffs); |
| |
| if (!bulkGetOpts.docs.length) { // optimization: skip empty requests |
| return; |
| } |
| |
| return src.bulkGet(bulkGetOpts).then(function (bulkGetResponse) { |
| /* istanbul ignore if */ |
| if (state.cancelled) { |
| throw new Error('cancelled'); |
| } |
| return Promise.all(bulkGetResponse.results.map(function (bulkGetInfo) { |
| return Promise.all(bulkGetInfo.docs.map(function (doc) { |
| var remoteDoc = doc.ok; |
| |
| if (doc.error) { |
| // when AUTO_COMPACTION is set, docs can be returned which look |
| // like this: {"missing":"1-7c3ac256b693c462af8442f992b83696"} |
| ok = false; |
| } |
| |
| if (!remoteDoc || !remoteDoc._attachments) { |
| return remoteDoc; |
| } |
| |
| return getDocAttachmentsFromTargetOrSource(target, src, remoteDoc).then(function (attachments) { |
| var filenames = Object.keys(remoteDoc._attachments); |
| attachments.forEach(function (attachment, i) { |
| var att = remoteDoc._attachments[filenames[i]]; |
| delete att.stub; |
| delete att.length; |
| att.data = attachment; |
| }); |
| |
| return remoteDoc; |
| }); |
| })); |
| })) |
| |
| .then(function (results) { |
| resultDocs = resultDocs.concat(flatten(results).filter(Boolean)); |
| }); |
| }); |
| } |
| |
| function hasAttachments(doc) { |
| return doc._attachments && Object.keys(doc._attachments).length > 0; |
| } |
| |
| function hasConflicts(doc) { |
| return doc._conflicts && doc._conflicts.length > 0; |
| } |
| |
| function fetchRevisionOneDocs(ids) { |
| // Optimization: fetch gen-1 docs and attachments in |
| // a single request using _all_docs |
| return src.allDocs({ |
| keys: ids, |
| include_docs: true, |
| conflicts: true |
| }).then(function (res) { |
| if (state.cancelled) { |
| throw new Error('cancelled'); |
| } |
| res.rows.forEach(function (row) { |
| if (row.deleted || !row.doc || !isGenOne(row.value.rev) || |
| hasAttachments(row.doc) || hasConflicts(row.doc)) { |
| // if any of these conditions apply, we need to fetch using get() |
| return; |
| } |
| |
| // strip _conflicts array to appease CSG (#5793) |
| /* istanbul ignore if */ |
| if (row.doc._conflicts) { |
| delete row.doc._conflicts; |
| } |
| |
| // the doc we got back from allDocs() is sufficient |
| resultDocs.push(row.doc); |
| delete diffs[row.id]; |
| }); |
| }); |
| } |
| |
| function getRevisionOneDocs() { |
| // filter out the generation 1 docs and get them |
| // leaving the non-generation one docs to be got otherwise |
| var ids = Object.keys(diffs).filter(function (id) { |
| var missing = diffs[id].missing; |
| return missing.length === 1 && isGenOne(missing[0]); |
| }); |
| if (ids.length > 0) { |
| return fetchRevisionOneDocs(ids); |
| } |
| } |
| |
| function returnResult() { |
| return { ok:ok, docs:resultDocs }; |
| } |
| |
| return Promise.resolve() |
| .then(getRevisionOneDocs) |
| .then(getAllDocs) |
| .then(returnResult); |
| } |
| |
| export default getDocs; |