<commit_before>/*jslint browser: true, undef: true*//*global Ext*/

/**
 * @abstract
 * An abstract class for singletons that facilitates communication with backend services
 *
 * TODO:
 * - add events for all lifecycle events: beforerequest, request, beforexception, exception, unauthorized
 * - does the touch version use Ext.Ajax or parent.request?
 * - pass through request options like touch version does
 */
Ext.define('Jarvus.util.AbstractAPI', {
    extend: 'Ext.data.Connection',

    config: {
        /**
         * @cfg {String/null}
         * A host to prefix URLs with, or null to leave paths domain-relative
         */
        host: null,

        /**
         * @cfg {Boolean}
         * True to use HTTPS when prefixing host. Only used if {@link #cfg-host} is set
         */
        useSSL: false,

        /**
         * @cfg {String/null}
         * A path to prefix URLs with
         */
        pathPrefix: null,

        // @inheritdoc
        withCredentials: true,

        // @inheritdoc
        useDefaultXhrHeader: false,

        // @inheritdoc
        disableCaching: false
    },

    //@private
    buildUrl: function(path) {
        var me = this,
            host = me.getHost(),
            pathPrefix = me.getPathPrefix();

        if (pathPrefix) {
            path = pathPrefix + path;
        }

        if (host) {
            path = (me.getUseSSL() ? 'https://' : 'http://') + host + path;
        }

        return path;
    },

    //@private
    buildHeaders: function(headers) {
        return headers;
    },

    //@private
    buildParams: function(params) {
        return params || null;
    },

    /**
     * Override {@link Ext.data.Connection#method-request} to implement auto-decoding and retry handler
     * @inheritdoc
     */
    request: function(options) {
        var me = this;

        return me.callParent([Ext.applyIf({
            url: me.buildUrl(options.url),
            params: me.buildParams(options.params),
            headers: me.buildHeaders(options.headers),
            timeout: options.timeout || 30000,
            success: function(response) {

                if (options.autoDecode !== false && response.getResponseHeader('Content-Type') == 'application/json') {
                    response.data = Ext.decode(response.responseText, true);
                }

                //Calling the callback function sending the decoded data
                Ext.callback(options.success, options.scope, [response]);

            },
            failure: function(response) {

                if (options.autoDecode !== false && response.getResponseHeader('Content-Type') == 'application/json') {
                    response.data = Ext.decode(response.responseText, true);
                }

                if (response.aborted === true) {
                    Ext.callback(options.abort, options.scope, [response]);
                } else if (response.status == 401 || response.statusText.indexOf('Unauthorized') !== -1) {

                    /*
                    We seem to always get the same session id, so we can't automatically try again once the user logs in
                    var oldSessionID = Ext.util.Cookies.get('s');
                     */

                    Ext.override(Ext.Msg, {
                        hide: function () {
                            var me = this,
                                hideManually = me.cfg ? me.cfg.hideManually : false;

                            if (!hideManually) {
                                me.callParent(arguments);
                            }
                        }
                    });

                    var msg = Ext.Msg.show({
                        hideManually: true,
                        title: 'Login Required',
                        msg: "You've either logged out or your has session expired. Please login and try again.",
                        buttonText: {
                            'yes': 'Login',
                            'no': 'Try Again',
                            'cancel': 'Cancel'
                        },
                        scope: msg,
                        fn: function (btn) {
                            if (btn === 'yes') {
                                // login
                                var loginWindow = window.open(me.buildUrl('/login'), 'emergence-login');
                                loginWindow.focus();
                                return;
                            } else if (btn === 'no') {
                                // try again
                                me.request(options);
                            }

                            msg.cfg.hideManually = false;
                            msg.hide();
                        }
                    });

                    /*
                    if (oldSessionID !== null) {
                        var cookieCheckInterval = window.setInterval(function() {
                            console.log(oldSessionID);
                            console.warn(Ext.util.Cookies.get('s'));
                            if (Ext.util.Cookies.get('s') != oldSessionID) {
                                alert('new login');
                                debugger;
                                window.clearInterval(cookieCheckInterval);
                            }
                        }, 100);
                    }
                    */
                } else if(response.status == 0) {
                    Ext.Msg.confirm('An error occurred', 'There was an error trying to reach the server. Do you want to try again?', function (btn) {
                        if (btn === 'yes') {
                            me.request(options);
                        }
                    });
                } else {
                    Ext.callback(options.failure, options.scope, [response]);
                }

            },
            scope: options.scope
        }, options)]);
    },

    // @deprecated
    setHostname: function(hostname) {
        //<debug>
        Ext.Logger.deprecate('hostname config is deprecated, use host instead');
        //</debug>

        this.setHost(hostname);
    },

    // @deprecated
    getHostname: function() {
        //<debug>
        Ext.Logger.deprecate('hostname config is deprecated, use host instead');
        //</debug>

        return this.getHost();
    }
});
<commit_msg>Detect fully-qualified URLs and don't auto-prefix them<commit_after>/*jslint browser: true, undef: true*//*global Ext*/

/**
 * @abstract
 * An abstract class for singletons that facilitates communication with backend services
 *
 * TODO:
 * - add events for all lifecycle events: beforerequest, request, beforexception, exception, unauthorized
 * - does the touch version use Ext.Ajax or parent.request?
 * - pass through request options like touch version does
 */
Ext.define('Jarvus.util.AbstractAPI', {
    extend: 'Ext.data.Connection',

    qualifiedUrlRe: /^(https?:)?\/\//,

    config: {
        /**
         * @cfg {String/null}
         * A host to prefix URLs with, or null to leave paths domain-relative
         */
        host: null,

        /**
         * @cfg {Boolean}
         * True to use HTTPS when prefixing host. Only used if {@link #cfg-host} is set
         */
        useSSL: false,

        /**
         * @cfg {String/null}
         * A path to prefix URLs with
         */
        pathPrefix: null,

        // @inheritdoc
        withCredentials: true,

        // @inheritdoc
        useDefaultXhrHeader: false,

        // @inheritdoc
        disableCaching: false
    },

    //@private
    buildUrl: function(path) {
        var me = this,
            host = me.getHost(),
            pathPrefix = me.getPathPrefix();

        if (me.qualifiedUrlRe.test(path)) {
            return path;
        }

        if (pathPrefix) {
            path = pathPrefix + path;
        }

        if (host) {
            path = (me.getUseSSL() ? 'https://' : 'http://') + host + path;
        }

        return path;
    },

    //@private
    buildHeaders: function(headers) {
        return headers;
    },

    //@private
    buildParams: function(params) {
        return params || null;
    },

    /**
     * Override {@link Ext.data.Connection#method-request} to implement auto-decoding and retry handler
     * @inheritdoc
     */
    request: function(options) {
        var me = this;

        return me.callParent([Ext.applyIf({
            url: me.buildUrl(options.url),
            params: me.buildParams(options.params),
            headers: me.buildHeaders(options.headers),
            timeout: options.timeout || 30000,
            success: function(response) {

                if (options.autoDecode !== false && response.getResponseHeader('Content-Type') == 'application/json') {
                    response.data = Ext.decode(response.responseText, true);
                }

                //Calling the callback function sending the decoded data
                Ext.callback(options.success, options.scope, [response]);

            },
            failure: function(response) {

                if (options.autoDecode !== false && response.getResponseHeader('Content-Type') == 'application/json') {
                    response.data = Ext.decode(response.responseText, true);
                }

                if (response.aborted === true) {
                    Ext.callback(options.abort, options.scope, [response]);
                } else if (response.status == 401 || response.statusText.indexOf('Unauthorized') !== -1) {

                    /*
                    We seem to always get the same session id, so we can't automatically try again once the user logs in
                    var oldSessionID = Ext.util.Cookies.get('s');
                     */

                    Ext.override(Ext.Msg, {
                        hide: function () {
                            var me = this,
                                hideManually = me.cfg ? me.cfg.hideManually : false;

                            if (!hideManually) {
                                me.callParent(arguments);
                            }
                        }
                    });

                    var msg = Ext.Msg.show({
                        hideManually: true,
                        title: 'Login Required',
                        msg: "You've either logged out or your has session expired. Please login and try again.",
                        buttonText: {
                            'yes': 'Login',
                            'no': 'Try Again',
                            'cancel': 'Cancel'
                        },
                        scope: msg,
                        fn: function (btn) {
                            if (btn === 'yes') {
                                // login
                                var loginWindow = window.open(me.buildUrl('/login'), 'emergence-login');
                                loginWindow.focus();
                                return;
                            } else if (btn === 'no') {
                                // try again
                                me.request(options);
                            }

                            msg.cfg.hideManually = false;
                            msg.hide();
                        }
                    });

                    /*
                    if (oldSessionID !== null) {
                        var cookieCheckInterval = window.setInterval(function() {
                            console.log(oldSessionID);
                            console.warn(Ext.util.Cookies.get('s'));
                            if (Ext.util.Cookies.get('s') != oldSessionID) {
                                alert('new login');
                                debugger;
                                window.clearInterval(cookieCheckInterval);
                            }
                        }, 100);
                    }
                    */
                } else if(response.status == 0) {
                    Ext.Msg.confirm('An error occurred', 'There was an error trying to reach the server. Do you want to try again?', function (btn) {
                        if (btn === 'yes') {
                            me.request(options);
                        }
                    });
                } else {
                    Ext.callback(options.failure, options.scope, [response]);
                }

            },
            scope: options.scope
        }, options)]);
    },

    // @deprecated
    setHostname: function(hostname) {
        //<debug>
        Ext.Logger.deprecate('hostname config is deprecated, use host instead');
        //</debug>

        this.setHost(hostname);
    },

    // @deprecated
    getHostname: function() {
        //<debug>
        Ext.Logger.deprecate('hostname config is deprecated, use host instead');
        //</debug>

        return this.getHost();
    }
});
<|endoftext|>


<commit_before>define(["app/collections/notes"], function (NotesCollection) {

  // TODO: Switch over to server-side tests
  // https://github.com/FormidableLabs/notes/pull/11
  describe.skip("app/collections/notes", function () {

    beforeEach(function () {
      // Create a reference for all internal suites/specs.
      this.notes = new NotesCollection();

      // Use internal method to clear out existing data.
      this.notes.localStorage._clear();
    });

    afterEach(function () {
      // Remove the reference.
      this.notes = null;
    });

    describe("creation", function () {

      it("has default values", function () {
        expect(this.notes).toBeTruthy();
        expect(this.notes.length).toBe(0);
      });

      // -- Omitted in Book. --
      it("should be empty on fetch", function (done) {
        // Stash reference to save context.
        var notes = this.notes;

        // Before fetch.
        expect(notes).toBeTruthy();
        expect(notes.length).toBe(0);

        // After fetch.
        notes.once("reset", function () {
          expect(notes.length).toBe(0);
          done();
        });

        notes.fetch({ reset: true });
      });

    });

    describe("modification", function () {

      beforeEach(function () {
        // Load a pre-existing note.
        this.notes.create({
          title: "Test note #1",
          text: "A pre-existing note from beforeEach."
        });
      });

      afterEach(function () {
        // Wipe internal data and reset collection.
        this.notes.localStorage._clear();
        this.notes.reset();
      });

      it("has a single note", function (done) {
        var notes = this.notes;

        // After fetch.
        notes.once("reset", function () {
          expect(notes.length).toBe(1);

          // Check model attributes.
          var note = notes.at(0);
          expect(notes).toBeTruthy();
          expect(note.get("title")).toContain("#1");
          expect(note.get("text")).toContain("pre-existing");

          done();
        });

        notes.fetch({ reset: true });
      });

      it("can delete a note", function (done) {
        var notes = this.notes;

        // After shift.
        notes.once("remove", function (note) {
          expect(note).toBeTruthy();
          expect(note.get("title")).toContain("#1");

          expect(notes.length).toBe(0);

          done();
        });

        // Remove and return first model.
        notes.shift();
      });

      // -- Omitted in Book. --
      it("can create a second note", function (done) {
        var notes = this.notes;

        // After fetch.
        notes.once("reset", function () {
          expect(notes.length).toBe(2);

          // Check model attributes.
          var note = notes.at(1);
          expect(notes).toBeTruthy();
          expect(note.get("title")).toContain("#2");
          expect(note.get("text")).toContain("new note");

          done();
        });

        notes.create({
          title: "Test note #2",
          text: "A new note, created in the test."
        });

        notes.fetch({ reset: true });
      });
    });
  });
});

/* Backbone Testing References
 *
 * **See**
 * http://backbone-testing.com/chapters/03/test/test.html
 * http://backbone-testing.com/notes/test/js/spec/collections/notes.spec.js
 * http://backbone-testing.com/notes/test/test.html?grep=App.Collections.Notes
 */
<commit_msg>app/collections/notes: port tests<commit_after>define(["app/collections/notes"], function (NotesCollection) {

  // TODO: Switch over to server-side tests
  // https://github.com/FormidableLabs/notes/pull/11
  describe("app/collections/notes", function () {

    before(function () {
      // Create a reference for all internal suites/specs.
      this.notes = new NotesCollection();

      // Use internal method to clear out existing data.
      this.notes.localStorage._clear();
    });

    after(function () {
      // Remove the reference.
      this.notes = null;
    });

    describe("creation", function () {

      it("has default values", function () {
        expect(this.notes).to.be.ok;
        expect(this.notes).to.have.length(0);
      });

      // -- Omitted in Book. --
      it("should be empty on fetch", function (done) {
        // Stash reference to save context.
        var notes = this.notes;

        // Before fetch.
        expect(notes).to.be.ok;
        expect(notes).to.have.length(0);

        // After fetch.
        notes.once("reset", function () {
          expect(notes).to.have.length(0);
          done();
        });

        notes.fetch({ reset: true });
      });

    });

    describe("modification", function () {

      beforeEach(function () {
        // Load a pre-existing note.
        this.notes.create({
          title: "Test note #1",
          text: "A pre-existing note from beforeEach."
        });
      });

      afterEach(function () {
        // Wipe internal data and reset collection.
        this.notes.localStorage._clear();
        this.notes.reset();
      });

      it("has a single note", function (done) {
        var notes = this.notes, note;

        // After fetch.
        notes.once("reset", function () {
          expect(notes).to.have.length(1);

          // Check model attributes.
          note = notes.at(0);
          expect(note).to.be.ok;
          expect(note.get("title")).to.contain("#1");
          expect(note.get("text")).to.contain("pre-existing");

          done();
        });

        notes.fetch({ reset: true });
      });

      it("can delete a note", function (done) {
        var notes = this.notes, note;

        // After shift.
        notes.once("remove", function () {
          expect(notes).to.have.length(0);
          done();
        });

        // Remove and return first model.
        note = notes.shift();
        expect(note).to.be.ok;
      });

      // -- Omitted in Book. --
      it("can create a second note", function (done) {
        var notes = this.notes,
          note = notes.create({
            title: "Test note #2",
            text: "A new note, created in the test."
          });

        // After fetch.
        notes.once("reset", function () {
          expect(notes).to.have.length(2);

          // Check model attributes.
          note = notes.at(1);
          expect(note).to.be.ok;
          expect(note.get("title")).to.contain("#2");
          expect(note.get("text")).to.contain("new note");

          done();
        });

        notes.fetch({ reset: true });
      });

    });
  });
});

/* Backbone Testing References
 *
 * **See**
 * http://backbone-testing.com/chapters/03/test/test.html
 * http://backbone-testing.com/notes/test/js/spec/collections/notes.spec.js
 * http://backbone-testing.com/notes/test/test.html?grep=App.Collections.Notes
 */
<|endoftext|>


<commit_before>/* Generated by Opal 0.6.3 */
var templateMap = {
    revealjs: {},
    deckjs: {}
};
(function ($opal) {
    var self = $opal.top, $scope = $opal, nil = $opal.nil, $breaker = $opal.breaker, $slice = $opal.slice, $module = $opal.module, $klass = $opal.klass, $hash2 = $opal.hash2;

    return (function ($base) {
        var self = $module($base, 'Asciidoctor');

        var def = self._proto, $scope = self._scope;

        (function ($base, $super) {
            function $RevealjsConverter() {
            };
            var self = $RevealjsConverter = $klass($base, $super, 'RevealjsConverter', $RevealjsConverter);

            var def = self._proto, $scope = self._scope;

            return (def.$convert = function (node, template_name, opts) {

                    var self = this;

                    window.nodee = node;

                    var obj = {};
                    obj.node = node;
                    obj.name = node.$node_name ? node.$node_name() : "";

                    // Cache disabled icin comment yap
                    //var templateMap = {
                    //    revealjs: {},
                    //    deckjs: {}
                    //};

                    var map = templateMap["revealjs"];

                    if (!map)
                        return "";

                    if (!map[obj.name]) {
                        var template = afx.getTemplate(obj.name + ".jade", "revealjs");
                        var fn = jade.compile(template, {pretty: true});
                        map[obj.name] = fn;
                    }

                    var result = map[obj.name](obj);

                    return result;

                    if (template_name == null) {
                        template_name = nil
                    }
                    if (opts == null) {
                        opts = $hash2([], {})
                    }
                    return nil;
                }, nil) && 'convert'
        })($scope.Converter, ($scope.Converter)._scope.Base)

    })(self)
})(Opal);<commit_msg>Remove window<commit_after>/* Generated by Opal 0.6.3 */
var templateMap = {
    revealjs: {},
    deckjs: {}
};
(function ($opal) {
    var self = $opal.top, $scope = $opal, nil = $opal.nil, $breaker = $opal.breaker, $slice = $opal.slice, $module = $opal.module, $klass = $opal.klass, $hash2 = $opal.hash2;

    return (function ($base) {
        var self = $module($base, 'Asciidoctor');

        var def = self._proto, $scope = self._scope;

        (function ($base, $super) {
            function $RevealjsConverter() {
            };
            var self = $RevealjsConverter = $klass($base, $super, 'RevealjsConverter', $RevealjsConverter);

            var def = self._proto, $scope = self._scope;

            return (def.$convert = function (node, template_name, opts) {

                    var self = this;

                    var obj = {};
                    obj.node = node;
                    obj.name = node.$node_name ? node.$node_name() : "";

                    // Cache disabled icin comment yap
                    //var templateMap = {
                    //    revealjs: {},
                    //    deckjs: {}
                    //};

                    var map = templateMap["revealjs"];

                    if (!map)
                        return "";

                    if (!map[obj.name]) {
                        var template = afx.getTemplate(obj.name + ".jade", "revealjs");
                        var fn = jade.compile(template, {pretty: true});
                        map[obj.name] = fn;
                    }

                    var result = map[obj.name](obj);

                    return result;

                    if (template_name == null) {
                        template_name = nil
                    }
                    if (opts == null) {
                        opts = $hash2([], {})
                    }
                    return nil;
                }, nil) && 'convert'
        })($scope.Converter, ($scope.Converter)._scope.Base)

    })(self)
})(Opal);<|endoftext|>


<commit_before>import {FefDebounceHelper} from '../classes/fef-debounce-helper';
import {FefResponsiveHelper} from '../classes/fef-responsive-helper';

const SCROLL_PAGER_CLASS = 'js-scroll-pager-container',
    INNER_CONTAINER_CLASS = 'js-scroll-pager-content',
    BUTTON_BACK_CLASS = 'js-scroll-pager-button-back',
    BUTTON_FORWARD_CLASS = 'js-scroll-pager-button-forward',
    BUTTON_ACTIVE_CLASS = 'subnav__pager--visible',
    MASK_LEFT_CLASS = 'js-subnav-mask-left',
    MASK_RIGHT_CLASS = 'js-subnav-mask-right',
    MASK_VISIBLE_CLASS = 'subnav__mask--visible',
    ITEM_ACTIVE_CLASS = 'js-active-subnav-item',
    DEBOUNCETIME = 10,
    RIGHT_OFFSET = 24,
    BUTTON_BACK_THRESHOLD = 2,
    INNER_CONTAINER_SCROLL_PADDING = 84,
    DEFAULT_SCROLL_TIME = 200;

export function init() {
    $('.'+SCROLL_PAGER_CLASS).each((index, element) => {
        new FefScrollPager($(element));
    });
}

/**
 * This component handles the paging buttons and paging functionality of a horizontally scrollable items list as well
 * as the masks for overflowing navigation items
 */
export class FefScrollPager {

    /**
     * @param $element jQuery element
     */
    constructor($element) {
        this.itemLeftPositions = new Array();
        this.itemRightPositions = new Array();
        this.$element = $element;
        this.$innerContainer = $(`.${INNER_CONTAINER_CLASS}`, this.$element);
        this.$buttonBack = $(`.${BUTTON_BACK_CLASS}`, this.$element);
        this.$buttonForward = $(`.${BUTTON_FORWARD_CLASS}`, this.$element);
        this.$maskLeft = $(`.${MASK_LEFT_CLASS}`, this.$element);
        this.$maskRight = $(`.${MASK_RIGHT_CLASS}`, this.$element);

        this.init();
        this.initItemPositions();
        this.registerListeners();

        // Immediately centering the nav bar doesn't work correctly - move to bottom of stack for correct results.
        setTimeout(() => {
            this.centerActiveItem();
        }, 0);
    }

    init() {
        this.updateButtonStatus();
        this.updateMaskStatus();
    }

    initItemPositions() {
        this.$innerContainer.children().each( (index, element) => {
            this.itemLeftPositions.push($(element).position().left);
            this.itemRightPositions.push($(element).position().left + $(element).innerWidth());
        });
    }

    registerListeners() {
        $(window).on('resize', FefDebounceHelper.debounce(() => this.init(), DEBOUNCETIME));
        this.$innerContainer.on('scroll', FefDebounceHelper.debounce(() => this.init(), DEBOUNCETIME));
        this.$buttonBack.on('click', () => { this.pageBack(); });
        this.$buttonForward.on('click', () => { this.pageForward(); });
    }

    updateButtonStatus() {
        if (FefResponsiveHelper.isDesktop() || FefResponsiveHelper.isDesktopWide()) {
            // show forward button if needed
            if (this.isAtScrollEnd()) {
                this.$buttonForward.removeClass(BUTTON_ACTIVE_CLASS);
            } else if (this.hasScrollableOverflow()) {
                this.$buttonForward.addClass(BUTTON_ACTIVE_CLASS);
            } else {
                this.$buttonForward.removeClass(BUTTON_ACTIVE_CLASS);
            }

            // show back button if needed
            if (this.hasScrollableOverflow() && this.$innerContainer.scrollLeft() > BUTTON_BACK_THRESHOLD) {
                this.$buttonBack.addClass(BUTTON_ACTIVE_CLASS);
            } else {
                this.$buttonBack.removeClass(BUTTON_ACTIVE_CLASS);
            }
        }
    }

    updateMaskStatus() {
        if (FefResponsiveHelper.isSmartphone() || FefResponsiveHelper.isTablet()) {
            // show right mask if needed
            if (this.isAtScrollEnd()) {
                this.$maskRight.removeClass(MASK_VISIBLE_CLASS);
            } else if (this.hasScrollableOverflow()) {
                this.$maskRight.addClass(MASK_VISIBLE_CLASS);
            } else {
                this.$maskRight.removeClass(MASK_VISIBLE_CLASS);
            }

            // show left mask if needed
            if (this.hasScrollableOverflow() && this.$innerContainer.scrollLeft() > BUTTON_BACK_THRESHOLD) {
                this.$maskLeft.addClass(MASK_VISIBLE_CLASS);
            } else {
                this.$maskLeft.removeClass(MASK_VISIBLE_CLASS);
            }
        }
    }

    pageForward() {
        let nextItem = 0;

        for (let i = 0; i < this.itemRightPositions.length; i++) {
            if (this.itemRightPositions[i] > this.$innerContainer.scrollLeft() + this.$innerContainer.innerWidth()) {
                nextItem = i;
                break;
            }
        }

        let newPosition = this.itemLeftPositions[nextItem] - INNER_CONTAINER_SCROLL_PADDING;
        this.scrollToPosition(newPosition);
    }

    pageBack() {
        let nextItem = 0;

        for (let i = 0; i < this.itemRightPositions.length; i++) {
            if (this.itemRightPositions[i] > this.$innerContainer.scrollLeft() + INNER_CONTAINER_SCROLL_PADDING) {
                nextItem = i;
                break;
            }
        }

        let newPosition = this.itemLeftPositions[nextItem] - this.$innerContainer.innerWidth() + INNER_CONTAINER_SCROLL_PADDING;
        this.scrollToPosition(newPosition);
    }

    centerActiveItem() {
        let $active = $(`.${ITEM_ACTIVE_CLASS}`, this.$element);

        if ($active.length !== 1) {
            return;
        }

        let containerLeftEdge = this.$innerContainer.offset().left,
            containerMiddle = containerLeftEdge + .5 * this.$innerContainer.outerWidth(),
            activeLeftEdge = $active.offset().left,
            activeMiddle = activeLeftEdge + .5 * $active.outerWidth(),
            diff = activeMiddle - containerMiddle,
            currentScroll = this.$innerContainer.scrollLeft(),
            newScrollPos = currentScroll + diff;

        this.scrollToPosition(newScrollPos);
    }

    scrollToPosition(position, time) {
        time = typeof time === 'undefined' ? DEFAULT_SCROLL_TIME : time;

        this.$innerContainer
            .stop(true, false)
            .animate( { scrollLeft: position }, time);
    }

    hasScrollableOverflow() {
        return this.$innerContainer[0].scrollWidth > this.$innerContainer.innerWidth() + RIGHT_OFFSET;
    }

    isAtScrollEnd() {
        return this.$innerContainer.scrollLeft() + this.$innerContainer.innerWidth() >= this.$innerContainer[0].scrollWidth;
    }
}
<commit_msg>refactoring to functional style<commit_after>import {FefDebounceHelper} from '../classes/fef-debounce-helper';
import {FefResponsiveHelper} from '../classes/fef-responsive-helper';

const SCROLL_PAGER_CLASS = 'js-scroll-pager-container',
    INNER_CONTAINER_CLASS = 'js-scroll-pager-content',
    BUTTON_BACK_CLASS = 'js-scroll-pager-button-back',
    BUTTON_FORWARD_CLASS = 'js-scroll-pager-button-forward',
    BUTTON_ACTIVE_CLASS = 'subnav__pager--visible',
    MASK_LEFT_CLASS = 'js-subnav-mask-left',
    MASK_RIGHT_CLASS = 'js-subnav-mask-right',
    MASK_VISIBLE_CLASS = 'subnav__mask--visible',
    ITEM_ACTIVE_CLASS = 'js-active-subnav-item',
    DEBOUNCETIME = 10,
    RIGHT_OFFSET = 24,
    BUTTON_BACK_THRESHOLD = 2,
    INNER_CONTAINER_SCROLL_PADDING = 84,
    DEFAULT_SCROLL_TIME = 200;

export function init() {
    $(`.${SCROLL_PAGER_CLASS}`).each((index, element) => {
        new FefScrollPager($(element));
    });
}

/**
 * This component handles the paging buttons and paging functionality of a horizontally scrollable items list as well
 * as the masks for overflowing navigation items
 */
export class FefScrollPager {

    /**
     * @param $element jQuery element
     */
    constructor($element) {
        this.itemLeftPositions = new Array();
        this.itemRightPositions = new Array();
        this.$element = $element;
        this.$innerContainer = $(`.${INNER_CONTAINER_CLASS}`, this.$element);
        this.$buttonBack = $(`.${BUTTON_BACK_CLASS}`, this.$element);
        this.$buttonForward = $(`.${BUTTON_FORWARD_CLASS}`, this.$element);
        this.$maskLeft = $(`.${MASK_LEFT_CLASS}`, this.$element);
        this.$maskRight = $(`.${MASK_RIGHT_CLASS}`, this.$element);

        this.init();
        this.initItemPositions();
        this.registerListeners();

        // Immediately centering the nav bar doesn't work correctly - move to bottom of stack for correct results.
        setTimeout(() => {
            this.centerActiveItem();
        }, 0);
    }

    init() {
        this.updateButtonStatus();
        this.updateMaskStatus();
    }

    initItemPositions() {
        this.$innerContainer.children().each( (index, element) => {
            this.itemLeftPositions.push($(element).position().left);
            this.itemRightPositions.push($(element).position().left + $(element).innerWidth());
        });
    }

    registerListeners() {
        $(window).on('resize', FefDebounceHelper.debounce(() => this.init(), DEBOUNCETIME));
        this.$innerContainer.on('scroll', FefDebounceHelper.debounce(() => this.init(), DEBOUNCETIME));
        this.$buttonBack.on('click', () => { this.pageBack(); });
        this.$buttonForward.on('click', () => { this.pageForward(); });
    }

    updateButtonStatus() {
        if (FefResponsiveHelper.isDesktop() || FefResponsiveHelper.isDesktopWide()) {
            // show forward button if needed
            if (this.isAtScrollEnd()) {
                this.$buttonForward.removeClass(BUTTON_ACTIVE_CLASS);
            } else if (this.hasScrollableOverflow()) {
                this.$buttonForward.addClass(BUTTON_ACTIVE_CLASS);
            } else {
                this.$buttonForward.removeClass(BUTTON_ACTIVE_CLASS);
            }

            // show back button if needed
            if (this.hasScrollableOverflow() && this.$innerContainer.scrollLeft() > BUTTON_BACK_THRESHOLD) {
                this.$buttonBack.addClass(BUTTON_ACTIVE_CLASS);
            } else {
                this.$buttonBack.removeClass(BUTTON_ACTIVE_CLASS);
            }
        }
    }

    updateMaskStatus() {
        if (FefResponsiveHelper.isSmartphone() || FefResponsiveHelper.isTablet()) {
            // show right mask if needed
            if (this.isAtScrollEnd()) {
                this.$maskRight.removeClass(MASK_VISIBLE_CLASS);
            } else if (this.hasScrollableOverflow()) {
                this.$maskRight.addClass(MASK_VISIBLE_CLASS);
            } else {
                this.$maskRight.removeClass(MASK_VISIBLE_CLASS);
            }

            // show left mask if needed
            if (this.hasScrollableOverflow() && this.$innerContainer.scrollLeft() > BUTTON_BACK_THRESHOLD) {
                this.$maskLeft.addClass(MASK_VISIBLE_CLASS);
            } else {
                this.$maskLeft.removeClass(MASK_VISIBLE_CLASS);
            }
        }
    }

    pageForward() {
        let visibleAreaRightEdge = this.$innerContainer.scrollLeft() + this.$innerContainer.innerWidth(),
            nextItem = this.itemRightPositions.findIndex(rightEdge => rightEdge > visibleAreaRightEdge),
            newPosition = this.itemLeftPositions[nextItem] - INNER_CONTAINER_SCROLL_PADDING;

        this.scrollToPosition(newPosition);
    }

    pageBack() {
        let visibleAreaLeftEdge = this.$innerContainer.scrollLeft() + INNER_CONTAINER_SCROLL_PADDING,
            nextItem = this.itemRightPositions.findIndex(rightEdge => rightEdge > visibleAreaLeftEdge),
            newPosition = this.itemLeftPositions[nextItem] - this.$innerContainer.innerWidth() + INNER_CONTAINER_SCROLL_PADDING;

        this.scrollToPosition(newPosition);
    }

    centerActiveItem() {
        let $active = $(`.${ITEM_ACTIVE_CLASS}`, this.$element);

        if ($active.length !== 1) {
            return;
        }

        let containerLeftEdge = this.$innerContainer.offset().left,
            containerMiddle = containerLeftEdge + .5 * this.$innerContainer.outerWidth(),
            activeLeftEdge = $active.offset().left,
            activeMiddle = activeLeftEdge + .5 * $active.outerWidth(),
            diff = activeMiddle - containerMiddle,
            currentScroll = this.$innerContainer.scrollLeft(),
            newScrollPos = currentScroll + diff;

        this.scrollToPosition(newScrollPos);
    }

    scrollToPosition(position, time) {
        time = typeof time === 'undefined' ? DEFAULT_SCROLL_TIME : time;

        this.$innerContainer
            .stop(true, false)
            .animate( { scrollLeft: position }, time);
    }

    hasScrollableOverflow() {
        return this.$innerContainer[0].scrollWidth > this.$innerContainer.innerWidth() + RIGHT_OFFSET;
    }

    isAtScrollEnd() {
        return this.$innerContainer.scrollLeft() + this.$innerContainer.innerWidth() >= this.$innerContainer[0].scrollWidth;
    }
}
<|endoftext|>


<commit_before>/*eslint no-console: ["error", { allow: ["warn", "error", "log"] }] */
/*
Simply logger to correctly error messages to systemd.
*/

const EMERG = 0,
    ALERT = 1,
    CRIT = 2,
    ERR = 3,
    WARNING = 4,
    NOTICE = 5,
    INFO = 6,
    DEBUG = 7;

var prefixes = ['<0>', '<1>', '<2>', '<3>', '<4>', '<5>', '<6>', '<7>'];

module.exports = {
    emerg: function(txt) {
        console.log(prefixes[EMERG], txt);
    },
    info: function(txt) {
        console.log(prefixes[INFO], txt);
    },
    err: function(txt) {
        console.log(prefixes[ERR], txt);
    },
    alert: function(txt) {
        console.log(prefixes[ALERT], txt);
    },
    crit: function(txt) {
        console.log(prefixes[CRIT], txt);
    },
    warn: function(txt) {
        console.log(prefixes[WARNING], txt);
    },
    notice: function(txt) {
        console.log(prefixes[NOTICE], txt);
    },
    debug: function(txt) {
        console.log(prefixes[DEBUG], txt);
    }
};<commit_msg>make the log behave more like console.log<commit_after>'use strict';
/*eslint no-console: ["error", { allow: ["warn", "error", "log"] }] */
/*
Simply logger to correctly error messages to systemd.
*/

const EMERG = 0,
      ALERT = 1,
      CRIT = 2,
      ERR = 3,
      WARNING = 4,
      NOTICE = 5,
      INFO = 6,
      DEBUG = 7;

var prefixes = ['<0>', '<1>', '<2>', '<3>', '<4>', '<5>', '<6>', '<7>'];

function log(level, args) {
    process.stdout.write(prefixes[level]+require('util').format.apply(null, args));
}

module.exports = {
    emerg: function() {
        log(EMERG, arguments);
    },
    alert: function() {
        log(ALERT, arguments);
    },
    crit: function() {
        log(CRIT, arguments);
    },
    err: function() {
        log(ERR, arguments);
    },
    warning: function() {
        log(WARNING, arguments);
    },
    notice: function() {
        log(NOTICE, arguments);
    },
    info: function() {
        log(INFO, arguments);
    },
    debug: function() {
        log(DEBUG, arguments);
    }
};
<|endoftext|>


<commit_before>import { tipRadius, freqScale } from "./globals";

export const gatherTips = (node, tips) => {

  if (typeof node.children !== "undefined") {
    for (let i = 0, c = node.children.length; i < c; i++) {
      gatherTips(node.children[i], tips);
    }
  } else {
    tips.push(node);
  }
  return tips;
};

export const getVaccines = (tips) => {
  const v = [];
  tips.forEach((tip) => {
    if (vaccineStrains.indexOf(tip.strain) !== -1) {
      tip.choice = vaccineChoice[tip.strain];
      v.push(tip);
    }
  });
  return v;
};

export const calcDates = (nodes) => {
  nodes.forEach((d) => {
    d.dateval = new Date(d.date);
  });
};

export const minimumAttribute = (node, attr, min) => {
  if (typeof node.children !== "undefined") {
    for (let i = 0, c = node.children.length; i < c; i++) {
      min = minimumAttribute(node.children[i], attr, min);
    }
  } else if (node[attr] < min) {
    min = node[attr];
  }
  return min;
};

export const maximumAttribute = (node, attr, max) => {
  if (typeof node.children !== "undefined") {
    for (let i = 0, c = node.children.length; i < c; i++) {
      max = maximumAttribute(node.children[i], attr, max);
    }
  } else if (node[attr] > max) {
    max = node[attr];
  }
  return max;
};

export const calcBranchLength = (node) => {
  if (typeof node.children !== "undefined") {
    for (let i = 0, c = node.children.length; i < c; i++) {
      calcBranchLength(node.children[i]);
      node.children[i].branch_length = node.children[i].xvalue - node.xvalue;
    }
  }
};

/**
 * for each node, calculate the number of subtending tips (alive or dead)
**/
export const calcFullTipCounts = (node) => {
  node.fullTipCount = 0;
  if (typeof node.children !== "undefined") {
    for (let i = 0; i < node.children.length; i++) {
      calcFullTipCounts(node.children[i]);
      node.fullTipCount += node.children[i].fullTipCount;
    }
  } else {
    node.fullTipCount = 1;
  }
};

/**
 * for each node, calculate the number of tips in the currently selected time window.
**/
export const calcTipCounts = (node) => {
  node.tipCount = 0;
  if (typeof node.children !== "undefined") {
    for (let i = 0; i < node.children.length; i++) {
      calcTipCounts(node.children[i]);
      node.tipCount += node.children[i].tipCount;
    }
  } else if (node.current) {
    node.tipCount = 1;
  }
  // console.log("node has children so iterate", node.tipCount, "number of children", node.children && node.children.length, node.current)
};

/**
sets each node in the tree to alive=true if it has at least one descendent with current=true
**/
export const setNodeAlive = (node) => {
  if (typeof node.children !== "undefined") {
    let aliveChildren = false;
    for (let i = 0, c = node.children.length; i < c; i++) {
      setNodeAlive(node.children[i]);
      aliveChildren = aliveChildren || node.children[i].alive;
    }
    node.alive = aliveChildren;
  } else {
    node.alive = node.current;
  }
};

export const adjust_freq_by_date = (nodes, rootNode) => {
  // console.log("all nodes and root node", nodes, rootNode)
  return nodes.map((d) => {
    // console.log("tipcount & rootnodeTipcount", d.tipCount, rootNode.tipCount)
    // d.frequency = (d.tipCount) / rootNode.tipCount;
  });
};

// export const arrayInEquality = function(a,b) {
//   if (a&&b){
//     const eq = a.map((d,i)=>d!==b[i]);
//     return eq.some((d)=>d);
//   }else{
//     return true;
//   }
// };

// branch thickness is from clade frequencies
export const branchThickness = function (tree) {
  if (tree.nodes) {
    const maxTipCount = tree.nodes[0].fullTipCount;
    return tree.nodes.map((d) => {
      return freqScale(d.fullTipCount / maxTipCount);
    });
  }
  return 2.0;
}

const getTipColorAttribute = function (node, colorScale, sequences) {
  if (colorScale.colorBy.slice(0, 3) === "gt-" && colorScale.genotype) {
    return getGenotype(colorScale.genotype[0][0],
                       colorScale.genotype[0][1],
                       node,
                       sequences.sequences);
  }
  return node.attr[colorScale.colorBy];
};

export const calcNodeColor = function (tree, colorScale, sequences) {
  if (tree && tree.nodes && colorScale && colorScale.colorBy) {
    const nodeColorAttr = tree.nodes.map((n) => getTipColorAttribute(n, colorScale, sequences));
    return nodeColorAttr.map((n) => colorScale.scale(n));
  }
  return null;
};

const determineLegendMatch = function (selectedLegendItem,
                                       node,
                                       legendBoundsMap,
                                       colorScale,
                                       sequences) {
  let bool;
  const nodeAttr = getTipColorAttribute(node, colorScale, sequences);
  // equates a tip and a legend element
  // exact match is required for categorical qunantities such as genotypes, regions
  // continuous variables need to fall into the interal (lower_bound[leg], leg]
  if (legendBoundsMap) {
    bool = (nodeAttr <= legendBoundsMap.upper_bound[selectedLegendItem]) &&
           (nodeAttr > legendBoundsMap.lower_bound[selectedLegendItem]);
  } else {
    bool = nodeAttr === selectedLegendItem;
  }
  return bool;
};

export const calcTipRadii = function (selectedLegendItem,
                           colorScale,
                           sequences,
                           tree
                         ) {
  if (selectedLegendItem && tree && tree.nodes){
    const legendMap = colorScale.continuous ? colorScale.legendBoundsMap : false;
    return tree.nodes.map((d) => determineLegendMatch(selectedLegendItem, d, legendMap, colorScale, sequences) ? 6 : 3);
  } else if (tree && tree.nodes) {
    return tree.nodes.map((d) => tipRadius);
  }
  return null; // fallthrough
};

const parseFilterQuery = function (query) {
  const tmp = query.split("-").map((d) => d.split("."));
  return {
    "fields": tmp.map((d) => d[0]),
    "filters": tmp.map((d) => d[d.length - 1].split(","))
  };
};

export const calcTipVisibility = function (tree, metaMetadata, controls) {
  if (tree.nodes){
    /* extract the filter information from redux.
    redux.filters has 2 keys, each with an array of values
    keys: "region" and/or "authors"
    filterPairs is a list of lists. Each list defines the filtering to do.
    i.e. [ [ region, [...values]], [authors, [...values]]]
    */
    const filterPairs = [];
    Object.keys(controls.filters).map((key) => {
      if (controls.filters[key].length) {
        filterPairs.push([key, controls.filters[key]]);
      }
    });

    const lowerLimit = controls.dateMin;
    const upperLimit = controls.dateMax;
    if (upperLimit && lowerLimit) {
      if (filterPairs.length) {
        return tree.nodes.map((d) => (
          d.attr.date >= lowerLimit
          && d.attr.date < upperLimit
          && filterPairs.every((x) => x[1].indexOf(d.attr[x[0]]) > -1)
        ) ? "visible" : "hidden");
      } else {
        return tree.nodes.map((d) => (
          d.attr.date >= lowerLimit
          && d.attr.date < upperLimit
        ) ? "visible" : "hidden");
      }
    }
  }
  return "visible";
};
<commit_msg>fix colorby genotype<commit_after>import { tipRadius, freqScale } from "./globals";
import { getGenotype } from "./getGenotype";

export const gatherTips = (node, tips) => {

  if (typeof node.children !== "undefined") {
    for (let i = 0, c = node.children.length; i < c; i++) {
      gatherTips(node.children[i], tips);
    }
  } else {
    tips.push(node);
  }
  return tips;
};

export const getVaccines = (tips) => {
  const v = [];
  tips.forEach((tip) => {
    if (vaccineStrains.indexOf(tip.strain) !== -1) {
      tip.choice = vaccineChoice[tip.strain];
      v.push(tip);
    }
  });
  return v;
};

export const calcDates = (nodes) => {
  nodes.forEach((d) => {
    d.dateval = new Date(d.date);
  });
};

export const minimumAttribute = (node, attr, min) => {
  if (typeof node.children !== "undefined") {
    for (let i = 0, c = node.children.length; i < c; i++) {
      min = minimumAttribute(node.children[i], attr, min);
    }
  } else if (node[attr] < min) {
    min = node[attr];
  }
  return min;
};

export const maximumAttribute = (node, attr, max) => {
  if (typeof node.children !== "undefined") {
    for (let i = 0, c = node.children.length; i < c; i++) {
      max = maximumAttribute(node.children[i], attr, max);
    }
  } else if (node[attr] > max) {
    max = node[attr];
  }
  return max;
};

export const calcBranchLength = (node) => {
  if (typeof node.children !== "undefined") {
    for (let i = 0, c = node.children.length; i < c; i++) {
      calcBranchLength(node.children[i]);
      node.children[i].branch_length = node.children[i].xvalue - node.xvalue;
    }
  }
};

/**
 * for each node, calculate the number of subtending tips (alive or dead)
**/
export const calcFullTipCounts = (node) => {
  node.fullTipCount = 0;
  if (typeof node.children !== "undefined") {
    for (let i = 0; i < node.children.length; i++) {
      calcFullTipCounts(node.children[i]);
      node.fullTipCount += node.children[i].fullTipCount;
    }
  } else {
    node.fullTipCount = 1;
  }
};

/**
 * for each node, calculate the number of tips in the currently selected time window.
**/
export const calcTipCounts = (node) => {
  node.tipCount = 0;
  if (typeof node.children !== "undefined") {
    for (let i = 0; i < node.children.length; i++) {
      calcTipCounts(node.children[i]);
      node.tipCount += node.children[i].tipCount;
    }
  } else if (node.current) {
    node.tipCount = 1;
  }
  // console.log("node has children so iterate", node.tipCount, "number of children", node.children && node.children.length, node.current)
};

/**
sets each node in the tree to alive=true if it has at least one descendent with current=true
**/
export const setNodeAlive = (node) => {
  if (typeof node.children !== "undefined") {
    let aliveChildren = false;
    for (let i = 0, c = node.children.length; i < c; i++) {
      setNodeAlive(node.children[i]);
      aliveChildren = aliveChildren || node.children[i].alive;
    }
    node.alive = aliveChildren;
  } else {
    node.alive = node.current;
  }
};

export const adjust_freq_by_date = (nodes, rootNode) => {
  // console.log("all nodes and root node", nodes, rootNode)
  return nodes.map((d) => {
    // console.log("tipcount & rootnodeTipcount", d.tipCount, rootNode.tipCount)
    // d.frequency = (d.tipCount) / rootNode.tipCount;
  });
};

// export const arrayInEquality = function(a,b) {
//   if (a&&b){
//     const eq = a.map((d,i)=>d!==b[i]);
//     return eq.some((d)=>d);
//   }else{
//     return true;
//   }
// };

// branch thickness is from clade frequencies
export const branchThickness = function (tree) {
  if (tree.nodes) {
    const maxTipCount = tree.nodes[0].fullTipCount;
    return tree.nodes.map((d) => {
      return freqScale(d.fullTipCount / maxTipCount);
    });
  }
  return 2.0;
}

const getTipColorAttribute = function (node, colorScale, sequences) {
  if (colorScale.colorBy.slice(0, 3) === "gt-" && colorScale.genotype) {
    return getGenotype(colorScale.genotype[0][0],
                       colorScale.genotype[0][1],
                       node,
                       sequences.sequences);
  }
  return node.attr[colorScale.colorBy];
};

export const calcNodeColor = function (tree, colorScale, sequences) {
  if (tree && tree.nodes && colorScale && colorScale.colorBy) {
    const nodeColorAttr = tree.nodes.map((n) => getTipColorAttribute(n, colorScale, sequences));
    return nodeColorAttr.map((n) => colorScale.scale(n));
  }
  return null;
};

const determineLegendMatch = function (selectedLegendItem,
                                       node,
                                       legendBoundsMap,
                                       colorScale,
                                       sequences) {
  let bool;
  const nodeAttr = getTipColorAttribute(node, colorScale, sequences);
  // equates a tip and a legend element
  // exact match is required for categorical qunantities such as genotypes, regions
  // continuous variables need to fall into the interal (lower_bound[leg], leg]
  if (legendBoundsMap) {
    bool = (nodeAttr <= legendBoundsMap.upper_bound[selectedLegendItem]) &&
           (nodeAttr > legendBoundsMap.lower_bound[selectedLegendItem]);
  } else {
    bool = nodeAttr === selectedLegendItem;
  }
  return bool;
};

export const calcTipRadii = function (selectedLegendItem,
                           colorScale,
                           sequences,
                           tree
                         ) {
  if (selectedLegendItem && tree && tree.nodes){
    const legendMap = colorScale.continuous ? colorScale.legendBoundsMap : false;
    return tree.nodes.map((d) => determineLegendMatch(selectedLegendItem, d, legendMap, colorScale, sequences) ? 6 : 3);
  } else if (tree && tree.nodes) {
    return tree.nodes.map((d) => tipRadius);
  }
  return null; // fallthrough
};

const parseFilterQuery = function (query) {
  const tmp = query.split("-").map((d) => d.split("."));
  return {
    "fields": tmp.map((d) => d[0]),
    "filters": tmp.map((d) => d[d.length - 1].split(","))
  };
};

export const calcTipVisibility = function (tree, metaMetadata, controls) {
  if (tree.nodes){
    /* extract the filter information from redux.
    redux.filters has 2 keys, each with an array of values
    keys: "region" and/or "authors"
    filterPairs is a list of lists. Each list defines the filtering to do.
    i.e. [ [ region, [...values]], [authors, [...values]]]
    */
    const filterPairs = [];
    Object.keys(controls.filters).map((key) => {
      if (controls.filters[key].length) {
        filterPairs.push([key, controls.filters[key]]);
      }
    });

    const lowerLimit = controls.dateMin;
    const upperLimit = controls.dateMax;
    if (upperLimit && lowerLimit) {
      if (filterPairs.length) {
        return tree.nodes.map((d) => (
          d.attr.date >= lowerLimit
          && d.attr.date < upperLimit
          && filterPairs.every((x) => x[1].indexOf(d.attr[x[0]]) > -1)
        ) ? "visible" : "hidden");
      } else {
        return tree.nodes.map((d) => (
          d.attr.date >= lowerLimit
          && d.attr.date < upperLimit
        ) ? "visible" : "hidden");
      }
    }
  }
  return "visible";
};
<|endoftext|>


<commit_before>import async from 'async';
import fs from 'fs';
import path from 'path';
import dockerode from 'dockerode';
import _ from 'underscore';
import util from './Util';
import hubUtil from './HubUtil';
import metrics from '../utils/MetricsUtil';
import containerServerActions from '../actions/ContainerServerActions';
import Promise from 'bluebird';
import rimraf from 'rimraf';

export default {
  host: null,
  client: null,
  placeholders: {},

  setup (ip, name) {
    if (!ip || !name) {
      throw new Error('Falsy ip or name passed to docker client setup');
    }

    let certDir = path.join(util.home(), '.docker/machine/machines/', name);
    if (!fs.existsSync(certDir)) {
      throw new Error('Certificate directory does not exist');
    }

    this.host = ip;
    this.client = new dockerode({
      protocol: 'https',
      host: ip,
      port: 2376,
      ca: fs.readFileSync(path.join(certDir, 'ca.pem')),
      cert: fs.readFileSync(path.join(certDir, 'cert.pem')),
      key: fs.readFileSync(path.join(certDir, 'key.pem'))
    });
  },

  init () {
    this.placeholders = JSON.parse(localStorage.getItem('placeholders')) || {};
    this.fetchAllContainers();
    this.listen();

    // Resume pulling containers that were previously being pulled
    _.each(_.values(this.placeholders), container => {
      containerServerActions.added({container});

      this.client.pull(container.Config.Image, (error, stream) => {
        if (error) {
          containerServerActions.error({name: container.Name, error});
          return;
        }

        stream.setEncoding('utf8');
        stream.on('data', function () {});
        stream.on('end', () => {
          if (!this.placeholders[container.Name]) {
            return;
          }

          delete this.placeholders[container.Name];
          localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
          this.createContainer(container.Name, {Image: container.Config.Image});
        });
      });
    });
  },

  startContainer (name, containerData) {
    let startopts = {
      Binds: containerData.Binds || []
    };

    if (containerData.NetworkSettings && containerData.NetworkSettings.Ports) {
      startopts.PortBindings = containerData.NetworkSettings.Ports;
    } else if (containerData.HostConfig && containerData.HostConfig.PortBindings) {
      startopts.PortBindings = containerData.HostConfig.PortBindings;
    } else {
      startopts.PublishAllPorts = true;
    }

    let container = this.client.getContainer(name);
    container.start(startopts, (error) => {
      if (error) {
        containerServerActions.error({name, error});
        return;
      }
      containerServerActions.started({name, error});
      this.fetchContainer(name);
    });
  },

  createContainer (name, containerData) {
    containerData.name = containerData.Name || name;

    if (containerData.Config && containerData.Config.Image) {
      containerData.Image = containerData.Config.Image;
    }

    if (!containerData.Env && containerData.Config && containerData.Config.Env) {
      containerData.Env = containerData.Config.Env;
    }

    containerData.Volumes = _.mapObject(containerData.Volumes, () => {return {};});

    let existing = this.client.getContainer(name);
    existing.kill(() => {
      existing.remove(() => {
        this.client.createContainer(containerData, (error) => {
          if (error) {
            containerServerActions.error({name, error});
            return;
          }
          metrics.track('Container Finished Creating');
          this.startContainer(name, containerData);
        });
      });
    });
  },

  fetchContainer (id) {
    this.client.getContainer(id).inspect((error, container) => {
      if (error) {
        containerServerActions.error({name: id, error});
      } else {
        container.Name = container.Name.replace('/', '');
        containerServerActions.updated({container});
      }
    });
  },

  fetchAllContainers () {
    this.client.listContainers({all: true}, (err, containers) => {
      if (err) {
        return;
      }
      async.map(containers, (container, callback) => {
        this.client.getContainer(container.Id).inspect((error, container) => {
          if (error) {
            callback(null, null);
            return;
          }
          container.Name = container.Name.replace('/', '');
          callback(null, container);
        });
      }, (err, containers) => {
        containers = containers.filter(c => c !== null);
        if (err) {
          // TODO: add a global error handler for this
          return;
        }
        containerServerActions.allUpdated({containers: _.indexBy(containers.concat(_.values(this.placeholders)), 'Name')});
      });
    });
  },

  run (name, repository, tag) {
    tag = tag || 'latest';
    let imageName = repository + ':' + tag;

    let placeholderData = {
      Id: util.randomId(),
      Name: name,
      Image: imageName,
      Config: {
        Image: imageName,
      },
      Tty: true,
      OpenStdin: true,
      State: {
        Downloading: true
      }
    };
    containerServerActions.added({container: placeholderData});

    this.placeholders[name] = placeholderData;
    localStorage.setItem('placeholders', JSON.stringify(this.placeholders));

    this.pullImage(repository, tag, error => {
      if (error) {
        containerServerActions.error({name, error});
        return;
      }

      if (!this.placeholders[name]) {
        return;
      }

      delete this.placeholders[name];
      localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
      this.createContainer(name, {Image: imageName, Tty: true, OpenStdin: true});
    },

    // progress is actually the progression PER LAYER (combined in columns)
    // not total because it's not accurate enough
    progress => {
      containerServerActions.progress({name, progress});
    },

    () => {
      containerServerActions.waiting({name, waiting: true});
    });
  },

  updateContainer (name, data) {
    let existing = this.client.getContainer(name);
    existing.inspect((error, existingData) => {
      if (error) {
        containerServerActions.error({name, error});
        return;
      }

      if (existingData.Config && existingData.Config.Image) {
        existingData.Image = existingData.Config.Image;
      }

      if (!existingData.Env && existingData.Config && existingData.Config.Env) {
        existingData.Env = existingData.Config.Env;
      }

      if ((!existingData.Tty || !existingData.OpenStdin) && existingData.Config && (existingData.Config.Tty || existingData.Config.OpenStdin)) {
        existingData.Tty = existingData.Config.Tty;
        existingData.OpenStdin = existingData.Config.OpenStdin;
      }

      var fullData = _.extend(existingData, data);
      this.createContainer(name, fullData);
    });
  },

  rename (name, newName) {
    this.client.getContainer(name).rename({name: newName}, error => {
      if (error && error.statusCode !== 204) {
        containerServerActions.error({name, error});
        return;
      }
      var oldPath = path.join(util.home(), 'Kitematic', name);
      var newPath = path.join(util.home(), 'Kitematic', newName);

      this.client.getContainer(newName).inspect((error, container) => {
        if (error) {
          // TODO: handle error
          containerServerActions.error({newName, error});
        }
        rimraf(newPath, () => {
          if (fs.existsSync(oldPath)) {
            fs.renameSync(oldPath, newPath);
          }
          var binds = _.pairs(container.Volumes).map(function (pair) {
            return pair[1] + ':' + pair[0];
          });
          var newBinds = binds.map(b => {
            return b.replace(path.join(util.home(), 'Kitematic', name), path.join(util.home(), 'Kitematic', newName));
          });
          this.updateContainer(newName, {Binds: newBinds});
          rimraf(oldPath, () => {});
        });
      });
    });
  },

  restart (name) {
    let container = this.client.getContainer(name);
    container.stop(error => {
      if (error && error.statusCode !== 304) {
        containerServerActions.error({name, error});
        return;
      }
      container.inspect((error, data) => {
        if (error) {
          containerServerActions.error({name, error});
        }
        this.startContainer(name, data);
      });
    });
  },

  stop (name) {
    this.client.getContainer(name).stop(error => {
      if (error && error.statusCode !== 304) {
        containerServerActions.error({name, error});
        return;
      }
      this.fetchContainer(name);
    });
  },

  start (name) {
    this.client.getContainer(name).start(error => {
      if (error && error.statusCode !== 304) {
        containerServerActions.error({name, error});
        return;
      }
      this.fetchContainer(name);
    });
  },

  destroy (name) {
    if (this.placeholders[name]) {
      containerServerActions.destroyed({id: name});
      delete this.placeholders[name];
      localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
      return;
    }

    let container = this.client.getContainer(name);
    container.unpause(function () {
      container.kill(function () {
        container.remove(function (error) {
          if (error) {
            containerServerActions.error({name, error});
            return;
          }
          containerServerActions.destroyed({id: name});
          var volumePath = path.join(util.home(), 'Kitematic', name);
          if (fs.existsSync(volumePath)) {
            rimraf(volumePath, () => {});
          }
        });
      });
    });
  },

  listen () {
    this.client.getEvents((error, stream) => {
      if (error || !stream) {
        // TODO: Add app-wide error handler
        return;
      }

      stream.setEncoding('utf8');
      stream.on('data', json => {
        let data = JSON.parse(json);

        if (data.status === 'pull' || data.status === 'untag' || data.status === 'delete') {
          return;
        }

        if (data.status === 'destroy') {
          containerServerActions.destroyed({id: data.id});
        } else if (data.id) {
          this.fetchContainer(data.id);
        }
      });
    });
  },

  pullImage (repository, tag, callback, progressCallback, blockedCallback) {
    let opts = {}, config = hubUtil.config();
    if (!hubUtil.config()) {
      opts = {};
    } else {
      let [username, password] = hubUtil.creds(config);
      opts = {
        authconfig: {
          username,
          password,
          auth: ''
        }
      };
    }

    this.client.pull(repository + ':' + tag, opts, (err, stream) => {
      if (err) {
        callback(err);
        return;
      }

      stream.setEncoding('utf8');

      // scheduled to inform about progression at given interval
      let tick = null;
      let layerProgress = {};

      // Split the loading in a few columns for more feedback
      let columns = {};
      columns.amount = 4; // arbitrary
      columns.toFill = 0; // the current column index, waiting for layer IDs to be displayed
      let error = null;

      // data is associated with one layer only (can be identified with id)
      stream.on('data', str => {
        var data = JSON.parse(str);

        if (data.error) {
          error = data.error;
          return;
        }

        if (data.status && (data.status === 'Pulling dependent layers' || data.status.indexOf('already being pulled by another client') !== -1)) {
          blockedCallback();
          return;
        }

        if (data.status === 'Pulling fs layer') {
          layerProgress[data.id] = {
            current: 0,
            total: 1
          };
        } else if (data.status === 'Downloading') {
          if (!columns.progress) {
            columns.progress = []; // layerIDs, nbLayers, maxLayers, progress value
            let layersToLoad = _.keys(layerProgress).length;
            let layersPerColumn = Math.floor(layersToLoad / columns.amount);
            let leftOverLayers = layersToLoad % columns.amount;
            for (let i = 0; i < columns.amount; i++) {
              let layerAmount = layersPerColumn;
              if (i < leftOverLayers) {
                layerAmount += 1;
              }
              columns.progress[i] = {layerIDs: [], nbLayers:0 , maxLayers: layerAmount, value: 0.0};
            }
          }

          layerProgress[data.id].current = data.progressDetail.current;
          layerProgress[data.id].total = data.progressDetail.total;

          // Assign to a column if not done yet
          if (!layerProgress[data.id].column) {
            // test if we can still add layers to that column
            if (columns.progress[columns.toFill].nbLayers === columns.progress[columns.toFill].maxLayers && columns.toFill < columns.amount - 1) {
              columns.toFill++;
            }

            layerProgress[data.id].column = columns.toFill;
            columns.progress[columns.toFill].layerIDs.push(data.id);
            columns.progress[columns.toFill].nbLayers++;
          }

          if (!tick) {
            tick = setTimeout(() => {
              clearInterval(tick);
              tick = null;
              for (let i = 0; i < columns.amount; i++) {
                columns.progress[i].value = 0.0;
                if (columns.progress[i].nbLayers > 0) {
                  let layer;
                  let totalSum = 0;
                  let currentSum = 0;

                  for (let j = 0; j < columns.progress[i].nbLayers; j++) {
                    layer = layerProgress[columns.progress[i].layerIDs[j]];
                    totalSum += layer.total;
                    currentSum += layer.current;
                  }

                  if (totalSum > 0) {
                    columns.progress[i].value = Math.min(100.0 * currentSum / totalSum, 100);
                  } else {
                    columns.progress[i].value = 0.0;
                  }
                }
              }
              progressCallback(columns);
            }, 16);
          }
        }
      });
      stream.on('end', function () {
        callback(error);
      });
    });
  },

  // TODO: move this to machine health checks
  waitForConnection (tries, delay) {
    tries = tries || 10;
    delay = delay || 1000;
    let tryCount = 1, connected = false;
    return new Promise((resolve, reject) => {
      async.until(() => connected, callback => {
        this.client.listContainers(error => {
          if (error) {
            if (tryCount > tries) {
              callback(Error('Cannot connect to the Docker Engine. Either the VM is not responding or the connection may be blocked (VPN or Proxy): ' + error.message));
            } else {
              tryCount += 1;
              setTimeout(callback, delay);
            }
          } else {
            connected = true;
            callback();
          }
        });
      }, error => {
        if (error) {
          reject(error);
        } else {
          resolve();
        }
      });
    });
  }
};
<commit_msg>Updated Container restart<commit_after>import async from 'async';
import fs from 'fs';
import path from 'path';
import dockerode from 'dockerode';
import _ from 'underscore';
import util from './Util';
import hubUtil from './HubUtil';
import metrics from '../utils/MetricsUtil';
import containerServerActions from '../actions/ContainerServerActions';
import Promise from 'bluebird';
import rimraf from 'rimraf';

export default {
  host: null,
  client: null,
  placeholders: {},

  setup (ip, name) {
    if (!ip || !name) {
      throw new Error('Falsy ip or name passed to docker client setup');
    }

    let certDir = path.join(util.home(), '.docker/machine/machines/', name);
    if (!fs.existsSync(certDir)) {
      throw new Error('Certificate directory does not exist');
    }

    this.host = ip;
    this.client = new dockerode({
      protocol: 'https',
      host: ip,
      port: 2376,
      ca: fs.readFileSync(path.join(certDir, 'ca.pem')),
      cert: fs.readFileSync(path.join(certDir, 'cert.pem')),
      key: fs.readFileSync(path.join(certDir, 'key.pem'))
    });
  },

  init () {
    this.placeholders = JSON.parse(localStorage.getItem('placeholders')) || {};
    this.fetchAllContainers();
    this.listen();

    // Resume pulling containers that were previously being pulled
    _.each(_.values(this.placeholders), container => {
      containerServerActions.added({container});

      this.client.pull(container.Config.Image, (error, stream) => {
        if (error) {
          containerServerActions.error({name: container.Name, error});
          return;
        }

        stream.setEncoding('utf8');
        stream.on('data', function () {});
        stream.on('end', () => {
          if (!this.placeholders[container.Name]) {
            return;
          }

          delete this.placeholders[container.Name];
          localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
          this.createContainer(container.Name, {Image: container.Config.Image});
        });
      });
    });
  },

  startContainer (name, containerData) {
    let startopts = {
      Binds: containerData.Binds || []
    };

    if (containerData.NetworkSettings && containerData.NetworkSettings.Ports) {
      startopts.PortBindings = containerData.NetworkSettings.Ports;
    } else if (containerData.HostConfig && containerData.HostConfig.PortBindings) {
      startopts.PortBindings = containerData.HostConfig.PortBindings;
    } else {
      startopts.PublishAllPorts = true;
    }

    let container = this.client.getContainer(name);
    container.start(startopts, (error) => {
      if (error) {
        containerServerActions.error({name, error});
        return;
      }
      containerServerActions.started({name, error});
      this.fetchContainer(name);
    });
  },

  createContainer (name, containerData) {
    containerData.name = containerData.Name || name;

    if (containerData.Config && containerData.Config.Image) {
      containerData.Image = containerData.Config.Image;
    }

    if (!containerData.Env && containerData.Config && containerData.Config.Env) {
      containerData.Env = containerData.Config.Env;
    }

    containerData.Volumes = _.mapObject(containerData.Volumes, () => {return {};});

    let existing = this.client.getContainer(name);
    existing.kill(() => {
      existing.remove(() => {
        this.client.createContainer(containerData, (error) => {
          if (error) {
            containerServerActions.error({name, error});
            return;
          }
          metrics.track('Container Finished Creating');
          this.startContainer(name, containerData);
        });
      });
    });
  },

  fetchContainer (id) {
    this.client.getContainer(id).inspect((error, container) => {
      if (error) {
        containerServerActions.error({name: id, error});
      } else {
        container.Name = container.Name.replace('/', '');
        containerServerActions.updated({container});
      }
    });
  },

  fetchAllContainers () {
    this.client.listContainers({all: true}, (err, containers) => {
      if (err) {
        return;
      }
      async.map(containers, (container, callback) => {
        this.client.getContainer(container.Id).inspect((error, container) => {
          if (error) {
            callback(null, null);
            return;
          }
          container.Name = container.Name.replace('/', '');
          callback(null, container);
        });
      }, (err, containers) => {
        containers = containers.filter(c => c !== null);
        if (err) {
          // TODO: add a global error handler for this
          return;
        }
        containerServerActions.allUpdated({containers: _.indexBy(containers.concat(_.values(this.placeholders)), 'Name')});
      });
    });
  },

  run (name, repository, tag) {
    tag = tag || 'latest';
    let imageName = repository + ':' + tag;

    let placeholderData = {
      Id: util.randomId(),
      Name: name,
      Image: imageName,
      Config: {
        Image: imageName,
      },
      Tty: true,
      OpenStdin: true,
      State: {
        Downloading: true
      }
    };
    containerServerActions.added({container: placeholderData});

    this.placeholders[name] = placeholderData;
    localStorage.setItem('placeholders', JSON.stringify(this.placeholders));

    this.pullImage(repository, tag, error => {
      if (error) {
        containerServerActions.error({name, error});
        return;
      }

      if (!this.placeholders[name]) {
        return;
      }

      delete this.placeholders[name];
      localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
      this.createContainer(name, {Image: imageName, Tty: true, OpenStdin: true});
    },

    // progress is actually the progression PER LAYER (combined in columns)
    // not total because it's not accurate enough
    progress => {
      containerServerActions.progress({name, progress});
    },

    () => {
      containerServerActions.waiting({name, waiting: true});
    });
  },

  updateContainer (name, data) {
    let existing = this.client.getContainer(name);
    existing.inspect((error, existingData) => {
      if (error) {
        containerServerActions.error({name, error});
        return;
      }

      if (existingData.Config && existingData.Config.Image) {
        existingData.Image = existingData.Config.Image;
      }

      if (!existingData.Env && existingData.Config && existingData.Config.Env) {
        existingData.Env = existingData.Config.Env;
      }

      if ((!existingData.Tty || !existingData.OpenStdin) && existingData.Config && (existingData.Config.Tty || existingData.Config.OpenStdin)) {
        existingData.Tty = existingData.Config.Tty;
        existingData.OpenStdin = existingData.Config.OpenStdin;
      }

      var fullData = _.extend(existingData, data);
      this.createContainer(name, fullData);
    });
  },

  rename (name, newName) {
    this.client.getContainer(name).rename({name: newName}, error => {
      if (error && error.statusCode !== 204) {
        containerServerActions.error({name, error});
        return;
      }
      var oldPath = path.join(util.home(), 'Kitematic', name);
      var newPath = path.join(util.home(), 'Kitematic', newName);

      this.client.getContainer(newName).inspect((error, container) => {
        if (error) {
          // TODO: handle error
          containerServerActions.error({newName, error});
        }
        rimraf(newPath, () => {
          if (fs.existsSync(oldPath)) {
            fs.renameSync(oldPath, newPath);
          }
          var binds = _.pairs(container.Volumes).map(function (pair) {
            return pair[1] + ':' + pair[0];
          });
          var newBinds = binds.map(b => {
            return b.replace(path.join(util.home(), 'Kitematic', name), path.join(util.home(), 'Kitematic', newName));
          });
          this.updateContainer(newName, {Binds: newBinds});
          rimraf(oldPath, () => {});
        });
      });
    });
  },

  restart (name) {
    this.client.getContainer(name).stop(stopError => {
      if (stopError && stopError.statusCode !== 304) {
        containerServerActions.error({name, stopError});
        return;
      }
      this.client.getContainer(name).start(startError => {
        if (startError && startError.statusCode !== 304) {
          containerServerActions.error({name, startError});
          return;
        }
        this.fetchContainer(name);
      });
    });
  },

  stop (name) {
    this.client.getContainer(name).stop(error => {
      if (error && error.statusCode !== 304) {
        containerServerActions.error({name, error});
        return;
      }
      this.fetchContainer(name);
    });
  },

  start (name) {
    this.client.getContainer(name).start(error => {
      if (error && error.statusCode !== 304) {
        containerServerActions.error({name, error});
        return;
      }
      this.fetchContainer(name);
    });
  },

  destroy (name) {
    if (this.placeholders[name]) {
      containerServerActions.destroyed({id: name});
      delete this.placeholders[name];
      localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
      return;
    }

    let container = this.client.getContainer(name);
    container.unpause(function () {
      container.kill(function () {
        container.remove(function (error) {
          if (error) {
            containerServerActions.error({name, error});
            return;
          }
          containerServerActions.destroyed({id: name});
          var volumePath = path.join(util.home(), 'Kitematic', name);
          if (fs.existsSync(volumePath)) {
            rimraf(volumePath, () => {});
          }
        });
      });
    });
  },

  listen () {
    this.client.getEvents((error, stream) => {
      if (error || !stream) {
        // TODO: Add app-wide error handler
        return;
      }

      stream.setEncoding('utf8');
      stream.on('data', json => {
        let data = JSON.parse(json);

        if (data.status === 'pull' || data.status === 'untag' || data.status === 'delete') {
          return;
        }

        if (data.status === 'destroy') {
          containerServerActions.destroyed({id: data.id});
        } else if (data.id) {
          this.fetchContainer(data.id);
        }
      });
    });
  },

  pullImage (repository, tag, callback, progressCallback, blockedCallback) {
    let opts = {}, config = hubUtil.config();
    if (!hubUtil.config()) {
      opts = {};
    } else {
      let [username, password] = hubUtil.creds(config);
      opts = {
        authconfig: {
          username,
          password,
          auth: ''
        }
      };
    }

    this.client.pull(repository + ':' + tag, opts, (err, stream) => {
      if (err) {
        callback(err);
        return;
      }

      stream.setEncoding('utf8');

      // scheduled to inform about progression at given interval
      let tick = null;
      let layerProgress = {};

      // Split the loading in a few columns for more feedback
      let columns = {};
      columns.amount = 4; // arbitrary
      columns.toFill = 0; // the current column index, waiting for layer IDs to be displayed
      let error = null;

      // data is associated with one layer only (can be identified with id)
      stream.on('data', str => {
        var data = JSON.parse(str);

        if (data.error) {
          error = data.error;
          return;
        }

        if (data.status && (data.status === 'Pulling dependent layers' || data.status.indexOf('already being pulled by another client') !== -1)) {
          blockedCallback();
          return;
        }

        if (data.status === 'Pulling fs layer') {
          layerProgress[data.id] = {
            current: 0,
            total: 1
          };
        } else if (data.status === 'Downloading') {
          if (!columns.progress) {
            columns.progress = []; // layerIDs, nbLayers, maxLayers, progress value
            let layersToLoad = _.keys(layerProgress).length;
            let layersPerColumn = Math.floor(layersToLoad / columns.amount);
            let leftOverLayers = layersToLoad % columns.amount;
            for (let i = 0; i < columns.amount; i++) {
              let layerAmount = layersPerColumn;
              if (i < leftOverLayers) {
                layerAmount += 1;
              }
              columns.progress[i] = {layerIDs: [], nbLayers:0 , maxLayers: layerAmount, value: 0.0};
            }
          }

          layerProgress[data.id].current = data.progressDetail.current;
          layerProgress[data.id].total = data.progressDetail.total;

          // Assign to a column if not done yet
          if (!layerProgress[data.id].column) {
            // test if we can still add layers to that column
            if (columns.progress[columns.toFill].nbLayers === columns.progress[columns.toFill].maxLayers && columns.toFill < columns.amount - 1) {
              columns.toFill++;
            }

            layerProgress[data.id].column = columns.toFill;
            columns.progress[columns.toFill].layerIDs.push(data.id);
            columns.progress[columns.toFill].nbLayers++;
          }

          if (!tick) {
            tick = setTimeout(() => {
              clearInterval(tick);
              tick = null;
              for (let i = 0; i < columns.amount; i++) {
                columns.progress[i].value = 0.0;
                if (columns.progress[i].nbLayers > 0) {
                  let layer;
                  let totalSum = 0;
                  let currentSum = 0;

                  for (let j = 0; j < columns.progress[i].nbLayers; j++) {
                    layer = layerProgress[columns.progress[i].layerIDs[j]];
                    totalSum += layer.total;
                    currentSum += layer.current;
                  }

                  if (totalSum > 0) {
                    columns.progress[i].value = Math.min(100.0 * currentSum / totalSum, 100);
                  } else {
                    columns.progress[i].value = 0.0;
                  }
                }
              }
              progressCallback(columns);
            }, 16);
          }
        }
      });
      stream.on('end', function () {
        callback(error);
      });
    });
  },

  // TODO: move this to machine health checks
  waitForConnection (tries, delay) {
    tries = tries || 10;
    delay = delay || 1000;
    let tryCount = 1, connected = false;
    return new Promise((resolve, reject) => {
      async.until(() => connected, callback => {
        this.client.listContainers(error => {
          if (error) {
            if (tryCount > tries) {
              callback(Error('Cannot connect to the Docker Engine. Either the VM is not responding or the connection may be blocked (VPN or Proxy): ' + error.message));
            } else {
              tryCount += 1;
              setTimeout(callback, delay);
            }
          } else {
            connected = true;
            callback();
          }
        });
      }, error => {
        if (error) {
          reject(error);
        } else {
          resolve();
        }
      });
    });
  }
};
<|endoftext|>


<commit_before>"use strict";

const Dict = require(ROOT_DIR +'lib/collection/Dict');
const MissionMgr = require(ROOT_DIR +'model/mission/MissionMgr');
const RobotMgr = require(ROOT_DIR +'model/robot/RobotMgr');
const ItemMgr = require(ROOT_DIR +'model/item/ItemMgr');

const MISSION_MGR = 1;
const ROBOT_MGR   = 2;
const ITEM_MGR    = 3;

module.exports = Role;

function Role(id) {
	this.id = id;
	this.name = null;
	this.rank = 0; // 积分
	this.money = 0; // 金币
	this.gem  = 0;
	this.p2pPort = 0;
	this.online = false;
	this.lastUse = Auxiliary.now(); // for gc
	//
	this.mgrDict = new Dict();
	this.mgrDict.add(MISSION_MGR,  {class:MissionMgr, obj:null, waitQueue:[]}); // 当有请求开始读取时状态时等待队列不为空，读完后赋值并按顺序回调
	this.mgrDict.add(ROBOT_MGR, {class:RobotMgr, obj:null, waitQueue:[]});
	this.mgrDict.add(ITEM_MGR, {class:ItemMgr, obj:null, waitQueue:[]});
}

var pro = Role.prototype;

pro.getId = function() {
	return this.id;
}

pro.getName = function() {
	return this.name;
}

pro.getRank = function() {
	return this.rank;
}

pro.setP2pPort = function(value) {
	this.p2pPort = value || 0;
}

pro.getP2pPort = function() {
	return this.p2pPort;
}

pro.setRank = function(value) {
	this.rank = value || 0;
}

pro.setMoney = function(value) {
	this.money = value || 0;
}

pro.setGem = function(value) {
	this.gem = value || 0;
}

pro.addGem = function(value) {
	this.gem += value || 0;
}

pro.minusGem = function(value) {
	value = value || 0;
	if (this.gem < value) {
		return false;
	}
	else {
		this.gem -= value;
		return true;
	}
}

pro.getGem = function() {
	return this.gem;
}

pro.addMoney = function(value) {
	this.money += value || 0;
}

pro.minusMoney = function(value) {
	value = value || 0;
	if (this.money < value) {
		return false;
	}
	else {
		this.money -= value;
		return true;
	}
}

pro.getMoney = function() {
	return this.money;
}

pro.getMissionMgr = function(cb) {
	this.getMgr(MISSION_MGR, cb);
}

pro.getRobotMgr = function(cb) {
	this.getMgr(ROBOT_MGR, cb);
}

pro.getItemMgr = function(cb) {
	this.getMgr(ITEM_MGR, cb);
}

pro.getMgr = function(key, cb) {
	let mgr = this.mgrDict.get(key);
	if ( mgr.obj === null ) {
		mgr.waitQueue.push(cb);
		if ( mgr.waitQueue.length > 1 ) {
			return;
		}
		let obj = new mgr.class(this.id);
		let waitQueue = mgr.waitQueue;
		obj.load(function(err, res) {
			if (err) {
				console.error(err);
				mgr.waitQueue = [];
				return Auxiliary.cbAll(waitQueue, [err]);
			}
			obj.afterLoad(function(err, res) {
				if (err) {
					console.error(err);
					mgr.waitQueue = [];
					return Auxiliary.cbAll(waitQueue, [err]);
				}
				mgr.waitQueue = [];
				mgr.obj = obj;
				Auxiliary.cbAll(waitQueue, [null, mgr.obj]);
			});
		});
	}
	else {
		cb(null, mgr.obj);
	}
}

// ===== 每个mgr类必须实现方法 =====

pro.register = function(cb) {
	let self = this;
	let total = this.mgrDict.getSize();
	//
	let count = 0;
	let mgrDict = this.mgrDict.getRaw();
	for ( let key in mgrDict ) {
		this.getMgr(key, function(err, mgr) {
			if (err) {
				console.error(err);
			}
			mgr.register(function(err, res) {
				if (err) {
					console.error(err);
				}
				++count;
				if (count >= total) {
					cb(null, self);
				}
			});
		});
	}
}

pro.save = function(cb) {
	let self = this;
	let total = this.mgrDict.getSize() + 1;
	let count = 0;
	//
	MysqlExtend.query('UPDATE tbl_role SET rank=?, money=?, gem=?, lastLogin=? WHERE id=?', [this.rank, this.money, this.gem, this.lastLogin, this.id], function (err, res) {
		if (err) {
			console.error(err);
		}
		++count;
		if (count >= total) {
			cb(null, self);
		}
	});
	//

	let mgrDict = this.mgrDict.getRaw();
	for ( let key in mgrDict ) {
		this.getMgr(key, function(err, mgr) {
			if (err) {
				console.error(err);
				++count;
				if (count >= total) {
					cb(null, self);
				}
				return;
			}
			mgr.save(function(err, res) {
				if (err) {
					console.error(err);
				}
				++count;
				if (count >= total) {
					cb(null, self);
				}
			});
		});
	}
}

pro.load = function(cb) {
	let self = this;
	MysqlExtend.query('SELECT name, rank, money, gem, regTime, lastLogin, p2pPort FROM tbl_role WHERE id=? LIMIT 1', [this.id], function (err, res) {
		if (err) {
			return cb(ErrorCode.LOGIN_ERROR);
		}
		let row        = res[0];
		self.name      = row.name;
		self.rank      = row.rank || 0;
		self.money     = row.money || 0;
		self.gem       = row.gem || 0;
		self.regTime   = row.regTime;
		self.lastLogin = row.lastLogin;
		self.p2pPort   = row.p2pPort || 0;
		self.first     = (self.regTime == self.lastLogin);
		if ( self.regTime == self.lastLogin ) {
			self.register(function(err, res) {
				if ( err ) {
					return cb(err);
				}
				self.afterLoad(cb);
			});
		}
		else {
			self.afterLoad(cb);
		}
	});
}

// load之后执行，数据之间有依赖的放在此处（如：机器人之间有队伍组合加成）
pro.afterLoad = function(cb) {
	this.lastLogin = Auxiliary.now();
	cb(null, this);
}

pro.setOnline = function(cb) {
	this.online = true;
	cb();
}

pro.setOffline = function(cb) {
	this.online = false;
	App.callRemote("pvp.PvpRemote.quit", this.id, {roleId:this.id}, Auxiliary.normalCb);
	cb();
}

pro.destory = function(cb) {
	cb();
}

pro.packLoginData = function(cb) {
	let self = this;
	let ret = {
		id    : this.id,
		name  : this.name,
		rank  : this.rank,
		money : this.money,
		gem   : this.gem,
		regTime   : this.regTime,
		lastLogin : this.lastLogin,
		first     : this.first || false
	};
	self.getRobotMgr(function(err, robotMgr) {
		if (err) {
			return cb(err);
		}
		ret.robotData = robotMgr.toData();
		self.getMissionMgr(function(err, missionMgr) {
			if (err) {
				return cb(err);
			}
			ret.missionData = missionMgr.toData();
			self.getItemMgr(function(err, itemMgr) {
				if (err) {
					return cb(err);
				}
				ret.itemData = itemMgr.toData();
				cb(null, ret);
			});
		});
	});
}

pro.stampLastUse = function() {
	this.lastUse = Auxiliary.now();
}

pro.setLastUse = function(value) {
	this.lastUse = value;
}

pro.getLastUse = function() {
	return this.lastUse;
}

<commit_msg>first<commit_after>"use strict";

const Dict = require(ROOT_DIR +'lib/collection/Dict');
const MissionMgr = require(ROOT_DIR +'model/mission/MissionMgr');
const RobotMgr = require(ROOT_DIR +'model/robot/RobotMgr');
const ItemMgr = require(ROOT_DIR +'model/item/ItemMgr');

const MISSION_MGR = 1;
const ROBOT_MGR   = 2;
const ITEM_MGR    = 3;

module.exports = Role;

function Role(id) {
	this.id = id;
	this.name = null;
	this.rank = 0; // 积分
	this.money = 0; // 金币
	this.gem  = 0;
	this.p2pPort = 0;
	this.online = false;
	this.lastUse = Auxiliary.now(); // for gc
	//
	this.mgrDict = new Dict();
	this.mgrDict.add(MISSION_MGR,  {class:MissionMgr, obj:null, waitQueue:[]}); // 当有请求开始读取时状态时等待队列不为空，读完后赋值并按顺序回调
	this.mgrDict.add(ROBOT_MGR, {class:RobotMgr, obj:null, waitQueue:[]});
	this.mgrDict.add(ITEM_MGR, {class:ItemMgr, obj:null, waitQueue:[]});
}

var pro = Role.prototype;

pro.getId = function() {
	return this.id;
}

pro.getName = function() {
	return this.name;
}

pro.getRank = function() {
	return this.rank;
}

pro.setP2pPort = function(value) {
	this.p2pPort = value || 0;
}

pro.getP2pPort = function() {
	return this.p2pPort;
}

pro.setRank = function(value) {
	this.rank = value || 0;
}

pro.setMoney = function(value) {
	this.money = value || 0;
}

pro.setGem = function(value) {
	this.gem = value || 0;
}

pro.addGem = function(value) {
	this.gem += value || 0;
}

pro.minusGem = function(value) {
	value = value || 0;
	if (this.gem < value) {
		return false;
	}
	else {
		this.gem -= value;
		return true;
	}
}

pro.getGem = function() {
	return this.gem;
}

pro.addMoney = function(value) {
	this.money += value || 0;
}

pro.minusMoney = function(value) {
	value = value || 0;
	if (this.money < value) {
		return false;
	}
	else {
		this.money -= value;
		return true;
	}
}

pro.getMoney = function() {
	return this.money;
}

pro.getMissionMgr = function(cb) {
	this.getMgr(MISSION_MGR, cb);
}

pro.getRobotMgr = function(cb) {
	this.getMgr(ROBOT_MGR, cb);
}

pro.getItemMgr = function(cb) {
	this.getMgr(ITEM_MGR, cb);
}

pro.getMgr = function(key, cb) {
	let mgr = this.mgrDict.get(key);
	if ( mgr.obj === null ) {
		mgr.waitQueue.push(cb);
		if ( mgr.waitQueue.length > 1 ) {
			return;
		}
		let obj = new mgr.class(this.id);
		let waitQueue = mgr.waitQueue;
		obj.load(function(err, res) {
			if (err) {
				console.error(err);
				mgr.waitQueue = [];
				return Auxiliary.cbAll(waitQueue, [err]);
			}
			obj.afterLoad(function(err, res) {
				if (err) {
					console.error(err);
					mgr.waitQueue = [];
					return Auxiliary.cbAll(waitQueue, [err]);
				}
				mgr.waitQueue = [];
				mgr.obj = obj;
				Auxiliary.cbAll(waitQueue, [null, mgr.obj]);
			});
		});
	}
	else {
		cb(null, mgr.obj);
	}
}

// ===== 每个mgr类必须实现方法 =====

pro.register = function(cb) {
	let self = this;
	let total = this.mgrDict.getSize();
	//
	let count = 0;
	let mgrDict = this.mgrDict.getRaw();
	for ( let key in mgrDict ) {
		this.getMgr(key, function(err, mgr) {
			if (err) {
				console.error(err);
			}
			mgr.register(function(err, res) {
				if (err) {
					console.error(err);
				}
				++count;
				if (count >= total) {
					cb(null, self);
				}
			});
		});
	}
}

pro.save = function(cb) {
	let self = this;
	let total = this.mgrDict.getSize() + 1;
	let count = 0;
	//
	MysqlExtend.query('UPDATE tbl_role SET rank=?, money=?, gem=?, lastLogin=? WHERE id=?', [this.rank, this.money, this.gem, this.lastLogin, this.id], function (err, res) {
		if (err) {
			console.error(err);
		}
		++count;
		if (count >= total) {
			cb(null, self);
		}
	});
	//

	let mgrDict = this.mgrDict.getRaw();
	for ( let key in mgrDict ) {
		this.getMgr(key, function(err, mgr) {
			if (err) {
				console.error(err);
				++count;
				if (count >= total) {
					cb(null, self);
				}
				return;
			}
			mgr.save(function(err, res) {
				if (err) {
					console.error(err);
				}
				++count;
				if (count >= total) {
					cb(null, self);
				}
			});
		});
	}
}

pro.load = function(cb) {
	let self = this;
	MysqlExtend.query('SELECT name, rank, money, gem, regTime, lastLogin, p2pPort FROM tbl_role WHERE id=? LIMIT 1', [this.id], function (err, res) {
		if (err) {
			return cb(ErrorCode.LOGIN_ERROR);
		}
		let row        = res[0];
		self.name      = row.name;
		self.rank      = row.rank || 0;
		self.money     = row.money || 0;
		self.gem       = row.gem || 0;
		self.regTime   = row.regTime;
		self.lastLogin = row.lastLogin;
		self.p2pPort   = row.p2pPort || 0;
		self.first     = (self.regTime == self.lastLogin);
		if ( self.regTime == self.lastLogin ) {
			self.register(function(err, res) {
				if ( err ) {
					return cb(err);
				}
				self.afterLoad(cb);
			});
		}
		else {
			self.afterLoad(cb);
		}
	});
}

// load之后执行，数据之间有依赖的放在此处（如：机器人之间有队伍组合加成）
pro.afterLoad = function(cb) {
	this.lastLogin = Auxiliary.now();
	cb(null, this);
}

pro.setOnline = function(cb) {
	this.online = true;
	cb();
}

pro.setOffline = function(cb) {
	this.online = false;
	App.callRemote("pvp.PvpRemote.quit", this.id, {roleId:this.id}, Auxiliary.normalCb);
	cb();
}

pro.destory = function(cb) {
	cb();
}

pro.packLoginData = function(cb) {
	let self = this;
	let ret = {
		id    : this.id,
		name  : this.name,
		rank  : this.rank,
		money : this.money,
		gem   : this.gem,
		regTime   : this.regTime,
		lastLogin : this.lastLogin,
		first     : this.first || false
	};
	this.first = false;
	self.getRobotMgr(function(err, robotMgr) {
		if (err) {
			return cb(err);
		}
		ret.robotData = robotMgr.toData();
		self.getMissionMgr(function(err, missionMgr) {
			if (err) {
				return cb(err);
			}
			ret.missionData = missionMgr.toData();
			self.getItemMgr(function(err, itemMgr) {
				if (err) {
					return cb(err);
				}
				ret.itemData = itemMgr.toData();
				cb(null, ret);
			});
		});
	});
}

pro.stampLastUse = function() {
	this.lastUse = Auxiliary.now();
}

pro.setLastUse = function(value) {
	this.lastUse = value;
}

pro.getLastUse = function() {
	return this.lastUse;
}

<|endoftext|>