var makeRequest = function(url, args, secure, callback) { var maxlen = 2048; var path = url + "?" + qs.stringify(args); if (path.length > maxlen) { throw new Error("Request too long for google to handle (2048 characters)."); } var options = { uri: (secure ? 'https' : 'http') + '://' + path }; if (GSV.config('proxy')) options.proxy = GSV.config('proxy'); if (typeof callback === 'function') { request(options, function (error, res, data) { if (error) { return callback(error); } if (res.statusCode === 200) { return callback(null, data); } return callback(new Error("Response status code: " + res.statusCode), data); }); } else if(typeof callback == 'string') { distfile = callback; if (GSV.config('dist')) distfile = GSV.config('dist') + "/" + callback; //TODO: async version? request(options).pipe(fs.createWriteStream(distfile)); } else { // console.log(typeof callback); } return options.uri; };
exports.persist = function (opts) { var db = this; var per = new events.EventEmitter(); per.TO = 1; per.FROM = 2; per.BOTH = 3; per.opts = { changes: {}, to: {}, from: {} }; // init to prevent undefined errors per.config = function (opts) { for (var i in opts) { per.opts[i] = opts[i]; } }; if (opts) { per.config(opts); } per.startingTimeout = opts && opts.startingTimeout ? opts.startingTimeout: STARTING_RETRY_TIMEOUT; per.maxTimeout = opts && opts.maxTimeout ? opts.maxTimeout: MAX_TIMEOUT; per.backoff = opts && opts.backoff ? opts.backoff : BACKOFF; per.connected = false; var vars = { retryTimeout: per.startingTimeout, replicating: false, connected: false }; var state = {}, replicating = false; state[per.TO] = vars; state[per.FROM] = clone(vars); function setup() { return db.info().then(function (info) { var d = per.opts.changes, opts = { since: info.update_seq, live: true }; if (d.opts) { opts = merge(opts, d.opts); } per.changes = db.changes(opts); }); } function addListeners(emitter, listeners) { listeners.forEach(function (listener) { var fn = emitter[listener['method']]; fn.call(emitter, listener['event'], listener['listener']); }); } // TODO: override window.XMLHttpRequest to test the following /* istanbul ignore next */ function backoff(retryTimeout) { return Math.min(per.maxTimeout, Math.floor(retryTimeout * per.backoff)); // exponential backoff } function disconnect() { per.connected = false; per.emit('disconnect'); } // TODO: override window.XMLHttpRequest to test the following /* istanbul ignore next */ function onError(err, direction) { if (err.status === 405) { // unknown error var s = state[direction]; s.connected = false; s.retryTimeout = backoff(s.retryTimeout); setTimeout(direction === per.TO ? replicateTo : replicateFrom, s.retryTimeout); if (per.connected) { disconnect(); } } } function connect() { per.connected = true; per.emit('connect'); } function onConnect(direction) { var s = state[direction]; s.connected = true; s.retryTimeout = per.startingTimeout; removeConnectListeners(direction); if (state[per.TO].connected && state[per.FROM].connected) { connect(); } } function removeConnectListeners(direction) { var emitter = direction === per.TO ? per.to : per.from; var connectListener = state[direction].connectListener; emitter.removeListener('change', connectListener); emitter.removeListener('complete', connectListener); emitter.removeListener('uptodate', connectListener); } function registerListeners(emitter, direction, listeners) { // TODO: override window.XMLHttpRequest to test the following /* istanbul ignore next */ emitter.on('error', function (err) { onError(err, direction); }); state[direction].connectListener = function () { onConnect(direction); }; var connectListener = state[direction].connectListener; emitter.once('change', connectListener) .once('complete', connectListener) .once('uptodate', connectListener); if (listeners) { addListeners(emitter, listeners); } } function replicate(direction) { var d = direction === per.TO ? per.opts.to : per.opts.from, method = direction === per.TO ? db.replicate.to : db.replicate.from; var opts = { live: true }, url = d.url ? d.url : per.opts.url; if (d.opts) { opts = merge(opts, d.opts); } if (direction === per.TO) { cancelTo(); } else { cancelFrom(); } var emitter = method(url, opts, d.onErr); if (direction === per.TO) { per.to = emitter; } else { per.from = emitter; } registerListeners(emitter, direction, d.listeners); } function replicateTo() { replicate(per.TO); } function replicateFrom() { replicate(per.FROM); } function startReplication(direction) { if (!state[per.TO].replicating && (direction === per.BOTH || direction === per.TO)) { state[per.TO].replicating = true; replicateTo(); } if (!state[per.FROM].replicating && (direction === per.BOTH || direction === per.FROM)) { state[per.FROM].replicating = true; replicateFrom(); } } per.start = function (direction) { direction = direction ? direction : per.BOTH; if (!replicating) { return setup().then(function () { replicating = true; startReplication(direction); }); } else { return new utils.Promise(function () { startReplication(direction); }); } }; function cancelChanges() { if (per.changes) { per.changes.cancel(); } } function cancelTo() { if (per.to) { per.to.cancel(); } } function cancelFrom() { if (per.from) { per.from.cancel(); } } per.cancel = function () { cancelChanges(); cancelTo(); cancelFrom(); }; per.stop = function (direction) { direction = direction ? direction : per.BOTH; if (direction === per.BOTH || direction === per.TO) { state[per.TO].replicating = false; state[per.TO].connected = false; cancelTo(); } if (direction === per.BOTH || direction === per.FROM) { state[per.FROM].replicating = false; state[per.FROM].connected = false; cancelFrom(); } if (!state[per.TO].replicating && !state[per.FROM].replicating) { cancelChanges(); } disconnect(); }; if (opts && !opts.manual) { per.start(); } return per; };