| 'use strict'; |
| var utils = require('./utils'); |
| var replication = require('./replicate'); |
| var replicate = replication.replicate; |
| var EE = require('events').EventEmitter; |
| |
| module.exports = Sync; |
| utils.inherits(Sync, EE); |
| function Sync(src, target, opts, callback) { |
| if (!(this instanceof Sync)) { |
| return new Sync(src, target, opts, callback); |
| } |
| var self = this; |
| if (typeof opts === 'function') { |
| callback = opts; |
| opts = {}; |
| } |
| if (typeof opts === 'undefined') { |
| opts = {}; |
| } |
| this.canceled = false; |
| opts = utils.clone(opts); |
| var onChange, complete; |
| if ('onChange' in opts) { |
| onChange = opts.onChange; |
| delete opts.onChange; |
| } |
| if (typeof callback === 'function' && !opts.complete) { |
| complete = callback; |
| } else if ('complete' in opts) { |
| complete = opts.complete; |
| delete opts.complete; |
| } |
| var srcPouch = replication.toPouch(src, opts); |
| var targPouch = replication.toPouch(target, opts); |
| this.push = replicate(srcPouch, targPouch, opts); |
| |
| this.pull = replicate(targPouch, srcPouch, opts); |
| var emittedCancel = false; |
| function onCancel(data) { |
| if (!emittedCancel) { |
| emittedCancel = true; |
| self.emit('cancel', data); |
| } |
| } |
| |
| function pullChange(change) { |
| self.emit('change', { |
| direction: 'pull', |
| change: change |
| }); |
| } |
| function pushChange(change) { |
| self.emit('change', { |
| direction: 'push', |
| change: change |
| }); |
| } |
| var listeners = {}; |
| |
| var removed = {}; |
| function removeAll(type) { // type is 'push' or 'pull' |
| return function (event, func) { |
| var isChange = event === 'change' && |
| (func === pullChange || func === pushChange); |
| var isCancel = event === 'cancel' && func === onCancel; |
| var isOtherEvent = event in listeners && func === listeners[event]; |
| |
| if (isChange || isCancel || isOtherEvent) { |
| if (!(event in removed)) { |
| removed[event] = {}; |
| } |
| removed[event][type] = true; |
| if (Object.keys(removed[event]).length === 2) { |
| // both push and pull have asked to be removed |
| self.removeAllListeners(event); |
| } |
| } |
| }; |
| } |
| |
| this.on('newListener', function (event) { |
| if (event === 'change') { |
| self.pull.on('change', pullChange); |
| self.push.on('change', pushChange); |
| } else if (event === 'cancel') { |
| self.pull.on('cancel', onCancel); |
| self.push.on('cancel', onCancel); |
| } else if (event !== 'error' && |
| event !== 'removeListener' && |
| event !== 'complete' && !(event in listeners)) { |
| listeners[event] = function (e) { |
| self.emit(event, e); |
| }; |
| self.pull.on(event, listeners[event]); |
| self.push.on(event, listeners[event]); |
| } |
| }); |
| |
| this.on('removeListener', function (event) { |
| if (event === 'change') { |
| self.pull.removeListener('change', pullChange); |
| self.push.removeListener('change', pushChange); |
| } else if (event === 'cancel') { |
| self.pull.removeListener('cancel', onCancel); |
| self.push.removeListener('cancel', onCancel); |
| } else if (event in listeners) { |
| if (typeof listeners[event] === 'function') { |
| self.pull.removeListener(event, listeners[event]); |
| self.push.removeListener(event, listeners[event]); |
| delete listeners[event]; |
| } |
| } |
| }); |
| |
| this.pull.on('removeListener', removeAll('pull')); |
| this.push.on('removeListener', removeAll('push')); |
| |
| var promise = utils.Promise.all([ |
| this.push, |
| this.pull |
| ]).then(function (resp) { |
| var out = { |
| push: resp[0], |
| pull: resp[1] |
| }; |
| self.emit('complete', out); |
| if (complete) { |
| complete(null, out); |
| } |
| self.removeAllListeners(); |
| return out; |
| }, function (err) { |
| self.cancel(); |
| self.emit('error', err); |
| if (complete) { |
| complete(err); |
| } |
| self.removeAllListeners(); |
| throw err; |
| }); |
| |
| this.then = function (success, err) { |
| return promise.then(success, err); |
| }; |
| |
| this.catch = function (err) { |
| return promise.catch(err); |
| }; |
| } |
| |
| Sync.prototype.cancel = function () { |
| if (!this.canceled) { |
| this.canceled = true; |
| this.push.cancel(); |
| this.pull.cancel(); |
| } |
| }; |