lib/sync.js

Summary

Maintainability
F
3 days
Test Coverage
'use strict';

require('colors');

var Q = require('q');
var read = require('read');
var http = require('http');
var https = require('https');
var VFS = require('./vfs').VFS;

var Client = (function(){

    var Options = {
        OVERWRITE_ALWAYS: 0,
        OVERWRITE_NEVER: 1,
        OVERWRITE_ASK: 2,
        OVERWRITE_IF_NEWER: 3
    };
    exports.Options = Options;

    var loc = function(isOriginRemote){
        return isOriginRemote ? 'remote' : 'local';
    };

    var logDiff = function(diff, simulate){
        var msg;
        if(diff.type === 'write') {
            if(diff.isOriginRemote) {
                msg = ('<< '.green) + ('↙ '.blue) + diff.query;
            } else {
                msg = ('>> '.green) + ('↗ '.blue) + diff.query;
            }
        } else {
            msg = ('✗'.red) + ' <' + loc(!diff.isOriginRemote) + '>/' + diff.query;
        }
        if(simulate === true) {
            msg += ' (simulate)';
        }
        if(diff.type === 'write') {
            console.log(msg);
        } else {
            console.log(msg.red);
        }
    };

    var addDiff = function(diff){
        return function(diffs){
            var defered = Q.defer();
            setTimeout(function() {
                diffs.push(diff);
                defered.resolve(diffs);
            }, 1);
            return defered.promise;
        };
    };


    function Client($28, projectEndpoint, projectToken, localProjectPath){
        this.vfs = new VFS($28, projectEndpoint, projectToken, localProjectPath);
    }

    Client.prototype.syncImpl = function(localQueries, remoteQueries, overwrite, deleteOrphaned, simulate, isOriginRemote){
        var that = this;
        var promises = [];
        var origin = isOriginRemote ? remoteQueries : localQueries;
        var target = isOriginRemote ? localQueries : remoteQueries;
        Object.keys(origin).forEach(function(query) {
            var targetQuery = target[query];
            var diff = {
                type: 'write',
                query: query,
                isOriginRemote: isOriginRemote
            };
            if(targetQuery) {
                var originLastModified = origin[query].lastModified;
                var targetLastModified = targetQuery.lastModified;
                if(overwrite === Options.OVERWRITE_ALWAYS) {
                    promises.push(addDiff(diff));
                } else if(overwrite === Options.OVERWRITE_IF_NEWER) {
                    if((new Date(originLastModified)).getTime() > (new Date(targetLastModified)).getTime()){
                        promises.push(addDiff(diff));
                    }
                } else if(overwrite === Options.OVERWRITE_ASK) {
                    promises.push(function (diffs) {
                        var defered = Q.defer();
                        read({
                            prompt: 'Overwrite: <' + loc(!isOriginRemote) + '>/' + query + ' (' + targetLastModified + ') with <' + loc(isOriginRemote) + '>/' + query + ' (' + originLastModified + ')? Y/[N]',
                            default: 'N'
                        }, function (er, value) {
                            if (er) {
                                defered.reject(er);
                            }
                            if (value !== undefined && value.toUpperCase() === 'Y') {
                                diffs.push(diff);
                            }
                            defered.resolve(diffs);
                        });
                        return defered.promise;
                    });
                } else if(overwrite === Options.OVERWRITE_NEVER) {
                    //Do nothing
                } else {
                    throw new Error('Invalid overwrite value: ' + overwrite);
                }
            } else {
                promises.push(addDiff(diff));
            }
        });
        if(deleteOrphaned === true) {
            Object.keys(target).forEach(function(query) {
                if(origin[query] === undefined) {
                    promises.push(addDiff({
                        type: 'delete',
                        query: query,
                        isOriginRemote: isOriginRemote
                    }));
                }
            });
        }
        promises.push(function(diffs){ return that.commit(simulate, diffs); });
        return promises.reduce(Q.when, new Q([]));
    };

    Client.prototype.writeLocalQuery = function(diff){
        return this.vfs.writeLocalQuery(diff.query).then(function(){ logDiff(diff); });
    };

    Client.prototype.writeRemoteQuery = function(diff){
        return this.vfs.writeRemoteQuery(diff.query)
            .then(function(compileError){
                logDiff(diff);
                if(compileError) {
                    compileError.query = diff.query;
                    throw compileError;
                }
            });
    };

    Client.prototype.deleteLocalQuery = function(diff){
        return this.vfs.deleteLocalQuery(diff.query).then(function(){ logDiff(diff); });
    };

    Client.prototype.deleteRemoteQuery = function(diff){
        return this.vfs.deleteRemoteQuery(diff.query).then(function(){ logDiff(diff); });
    };

    Client.prototype.commit = function(simulate, diffs){
        var that = this;
        var modulePromises = [];
        var queryPromises = [];
        diffs.forEach(function(diff){
            var container = diff.query.substring(0, 'modules'.length) === 'modules' ? modulePromises : queryPromises;
            if(simulate) {
                logDiff(diff, simulate);
            } else if(diff.type === 'write'){
                if(diff.isOriginRemote) {
                    container.push(function(){ return that.writeLocalQuery(diff); });
                } else {
                    container.push(function(){ return that.writeRemoteQuery(diff); });
                }
            } else {
                if(diff.isOriginRemote) {
                    container.push(function(){ return that.deleteLocalQuery(diff); });
                } else {
                    container.push(function(){ return that.deleteRemoteQuery(diff); });
                }
            }
        });

        var handler = function(results){
            var errors = [];
            results.forEach(function (result) {
                if (result.state !== 'fulfilled') {
                    errors.push(result.reason);
                }
            });
            if(errors.length > 0) {
                throw errors;
            }
            return results;
        };

        return [
            function(){
                var promises = [];
                modulePromises.forEach(function(promise){
                    promises.push(promise());
                });
                return Q.allSettled(promises).then(handler);
            },
            function(){
                var promises = [];
                queryPromises.forEach(function(promise){
                    promises.push(promise());
                });
                return Q.allSettled(promises).then(handler);
            }
        ].reduce(Q.when, new Q([]));
    };

    Client.prototype.sync = function(overwrite, deleteOrphaned, simulate, ignore, isOriginRemote, concurrency){
        if (concurrency === undefined) {
          http.globalAgent.maxSockets = 8;
          https.globalAgent.maxSockets = 8;
          console.log('Concurrency limit: 8.'.grey);
        }
        else {
          http.globalAgent.maxSockets = concurrency;
          https.globalAgent.maxSockets = concurrency;
          console.log('Concurrency limit: ' + concurrency + '.'.grey);
        }
        var that = this;
        return Q.all([
            this.vfs.getLocalQueries(),
            this.vfs.getRemoteQueries()
        ])
            .then(function(queries){
                var localQueries  = ignore(queries[0]);
                var remoteQueries = ignore(queries[1]);
                return that.syncImpl(localQueries, remoteQueries, overwrite, deleteOrphaned, simulate, isOriginRemote);
            });
    };

    Client.prototype.download = function(overwrite, deleteOrphaned, simulate, ignore, concurrency){
        return this.sync(overwrite, deleteOrphaned, simulate, ignore, true, concurrency);
    };

    Client.prototype.upload = function(overwrite, deleteOrphaned, simulate, ignore, concurrency){
        return this.sync(overwrite, deleteOrphaned, simulate, ignore, false, concurrency);
    };

    Client.prototype.truncate = function(simulate, ignore){
        var that = this;
        return this.vfs.getRemoteQueries().then(function(queries){
            var remoteQueries = ignore(queries);
            return that.syncImpl({}, remoteQueries, Options.OVERWRITE_ALWAYS, true, simulate, false);
        });
    };

    return Client;
})();

exports.Client = Client;