<commit_before>import Promise from 'promise';
import browserInstallations from '../browser/installations';
import BrowserConnection from '../browser/connection';
import LocalBrowserConnection from '../browser/local-connection';
import { MESSAGES, getText } from '../messages';

export default class Bootstrapper {
    static BROWSER_CONNECTION_READY_TIMEOUT = 30 * 1000;

    constructor (browserConnectionGateway) {
        this.browserConnectionGateway = browserConnectionGateway;

        this.src             = [];
        this.browsers        = [];
        this.filter          = null;
        this.reporter        = null;
        this.reportOutStream = null;
    }

    static _convertBrowserAliasToBrowserInfo (browser) {
        if (typeof browser !== 'string')
            return browser;

        var browserInfo = browserInstallations.getInfo(browser);

        if (!browserInfo)
            throw new Error(getText(MESSAGES.cantFindBrowserForAlias, browser));

        return browserInfo;
    }

    static async _waitBrowserConnectionsReady (browserConnections) {
        var readyTimeout = setTimeout(() => {
            throw new Error(getText(MESSAGES.cantEstablishBrowserConnection));
        }, Bootstrapper.BROWSER_CONNECTION_READY_TIMEOUT);

        var onError = msg => {
            throw new Error(msg);
        };

        browserConnections.forEach(bc => bc.once('error', onError));

        await * browserConnections
            .filter(bc => !bc.ready)
            .map(bc => new Promise(resolve => bc.once('ready', resolve)));

        browserConnections.forEach(bc => bc.removeListener('error', onError));
        clearTimeout(readyTimeout);
    }

    _createConnectionFromBrowserInfo (browser) {
        if (browser instanceof BrowserConnection)
            return browser;

        return new LocalBrowserConnection(this.browserConnectionGateway, browser);
    }

    async _getBrowserConnections () {
        var browserConnections = this.browsers
            .map(Bootstrapper._convertBrowserAliasToBrowserInfo)
            .map(browser => this._createConnectionFromBrowserInfo(browser));

        await Bootstrapper._waitBrowserConnectionsReady(browserConnections);

        return browserConnections;
    }

    async _getTests () {
        //TODO
    }

    _createReporter () {
        //TODO
    }

    // API
    static freeBrowserConnections (browserConnections, errorHandler) {
        browserConnections.forEach(bc => {
            bc.removeListener('error', errorHandler);

            if (bc instanceof LocalBrowserConnection)
                bc.close();
        });
    }

    async createRunnableConfiguration () {
        var reporter = this._createReporter();

        var [browserConnections, tests] = await * [
            this._getBrowserConnections(),
            this._getTests()
        ];

        return { reporter, browserConnections, tests };
    }
}
<commit_msg>Async event errors dispatching done right<commit_after>import Promise from 'promise';
import browserInstallations from '../browser/installations';
import BrowserConnection from '../browser/connection';
import LocalBrowserConnection from '../browser/local-connection';
import { MESSAGES, getText } from '../messages';

export default class Bootstrapper {
    static BROWSER_CONNECTION_READY_TIMEOUT = 30 * 1000;

    constructor (browserConnectionGateway) {
        this.browserConnectionGateway = browserConnectionGateway;

        this.src             = [];
        this.browsers        = [];
        this.filter          = null;
        this.reporter        = null;
        this.reportOutStream = null;
    }

    static _convertBrowserAliasToBrowserInfo (browser) {
        if (typeof browser !== 'string')
            return browser;

        var browserInfo = browserInstallations.getInfo(browser);

        if (!browserInfo)
            throw new Error(getText(MESSAGES.cantFindBrowserForAlias, browser));

        return browserInfo;
    }

    static _waitBrowserConnectionsReady (browserConnections) {
        return new Promise((resolve, reject) => {
            var readyTimeout = setTimeout(() => {
                reject(new Error(getText(MESSAGES.cantEstablishBrowserConnection)));
            }, Bootstrapper.BROWSER_CONNECTION_READY_TIMEOUT);

            var onError = msg => {
                reject(new Error(msg));
            };

            browserConnections.forEach(bc => bc.once('error', onError));

            var readyPromises = browserConnections
                .filter(bc => !bc.ready)
                .map(bc => new Promise(resolve => bc.once('ready', resolve)));

            Promise.all(readyPromises).then(() => {
                browserConnections.forEach(bc => bc.removeListener('error', onError));
                clearTimeout(readyTimeout);
                resolve();
            });
        });
    }

    _createConnectionFromBrowserInfo (browser) {
        if (browser instanceof BrowserConnection)
            return browser;

        return new LocalBrowserConnection(this.browserConnectionGateway, browser);
    }

    async _getBrowserConnections () {
        var browserConnections = this.browsers
            .map(Bootstrapper._convertBrowserAliasToBrowserInfo)
            .map(browser => this._createConnectionFromBrowserInfo(browser));

        await Bootstrapper._waitBrowserConnectionsReady(browserConnections);

        return browserConnections;
    }

    async _getTests () {
        //TODO
    }

    _createReporter () {
        //TODO
    }

    // API
    async createRunnableConfiguration () {
        var reporter = this._createReporter();

        var [browserConnections, tests] = await * [
            this._getBrowserConnections(),
            this._getTests()
        ];

        return { reporter, browserConnections, tests };
    }
}
<|endoftext|>


<commit_before>import $ from 'jquery';
import _ from 'underscore';
import app from '../../../app';
import loadTemplate from '../../../utils/loadTemplate';
import '../../../lib/select2';
import '../../../lib/whenAll.jquery';
import { isMultihash } from '../../../utils';
import baseVw from '../../baseVw';
import Moderators from '../../components/Moderators';
import { openSimpleMessage } from '../SimpleMessage';

export default class extends baseVw {
  constructor(options = {}) {
    const opts = {
      className: 'settingsStore',
      ...options,
    };
    super(opts);
    this.options = opts;

    this.profile = app.profile.clone();

    // Sync our clone with any changes made to the global profile.
    this.listenTo(app.profile, 'someChange',
      (md, pOpts) => this.profile.set(pOpts.setAttrs));

    // Sync the global profile with any changes we save via our clone.
    this.listenTo(this.profile, 'sync',
      (md, resp, pOpts) => app.profile.set(this.profile.toJSON(pOpts.attrs)));

    this.settings = app.settings.clone();

    // Sync our clone with any changes made to the global settings model.
    this.listenTo(app.settings, 'someChange',
      (md, sOpts) => this.settings.set(sOpts.setAttrs));

    // Sync the global settings model with any changes we save via our clone.
    this.listenTo(this.settings, 'sync',
      (md, resp, sOpts) => app.settings.set(this.settings.toJSON(sOpts.attrs)));

    this.currentMods = this.settings.get('storeModerators');
    this._showVerifiedOnly = true;

    this.modsSelected = new Moderators({
      fetchErrorTitle: app.polyglot.t('settings.storeTab.errors.selectedModsTitle'),
      cardState: 'selected',
      notSelected: 'deselected',
      showInvalid: true,
      controlsOnInvalid: true,
      showSpinner: false,
    });

    this.modsByID = new Moderators({
      async: false,
      fetchErrorTitle: app.polyglot.t('settings.storeTab.errors.modNotFoundTitle'),
      excludeIDs: this.currentMods,
      showInvalid: true,
      wrapperClasses: 'noMin',
      showSpinner: false,
    });

    this.listenTo(this.modsByID, 'noModsFound', (mOpts) => this.noModsByIDFound(mOpts.guids));

    this.modsAvailable = new Moderators({
      apiPath: 'moderators',
      excludeIDs: this.currentMods,
      showVerifiedOnly: true,
      fetchErrorTitle: app.polyglot.t('settings.storeTab.errors.availableModsTitle'),
      showLoadBtn: true,
    });
  }

  events() {
    return {
      'click .js-browseMods': 'fetchAvailableModerators',
      'click .js-submitModByID': 'clickSubmitModByID',
      'click .js-save': 'save',
      'click .js-storeVerifiedOnly': 'onClickVerifiedOnly',
    };
  }

  noModsByIDFound(guids) {
    const modsNotFound = app.polyglot.t('settings.storeTab.errors.modsNotFound',
      { guids, smart_count: guids.length });
    this.showModByIDError(modsNotFound);
    if (this.modsByID.modCount === 0) {
      this.getCachedEl('.js-modListAvailable').addClass('hide');
    }
  }

  fetchAvailableModerators() {
    // get the verified mods via POST
    this.modsAvailable.getModeratorsByID({
      moderatorIDs: app.verifiedMods.pluck('peerID'),
      useCache: true,
      method: 'POST',
      apiPath: 'fetchprofiles',
    });
    // get random mods via GET
    this.modsAvailable.getModeratorsByID();
    this.getCachedEl('.js-modListAvailable').removeClass('hide');
    this.getCachedEl('.js-noModsAdded').addClass('hide');
  }

  showModByIDError(msg) {
    this.getCachedEl('.js-submitModByIDInputError').removeClass('hide');
    this.getCachedEl('.js-submitModByIDInputErrorText').text(msg);
  }

  clickSubmitModByID() {
    let modID = this.getCachedEl('.js-submitModByIDInput').val();

    this.getCachedEl('.js-submitModByIDInputError').addClass('hide');

    if (modID) {
      // trim unwanted copy and paste characters
      modID = modID.replace('ob://', '');
      modID = modID.split('/')[0];
      modID = modID.trim();

      if (isMultihash(modID)) {
        if (!this.currentMods.includes(modID)) {
          if (modID !== app.profile.id) {
            this.modsByID.getModeratorsByID({ moderatorIDs: [modID] });
            this.getCachedEl('.js-modListByID').removeClass('hide');
          } else {
            const ownGUID = app.polyglot.t('settings.storeTab.errors.ownGUID', { guid: modID });
            this.showModByIDError(ownGUID);
          }
        } else {
          const dupeGUID = app.polyglot.t('settings.storeTab.errors.dupeGUID', { guid: modID });
          this.showModByIDError(dupeGUID);
        }
      } else {
        const notGUID = app.polyglot.t('settings.storeTab.errors.notGUID', { guid: modID });
        this.showModByIDError(notGUID);
      }
    } else {
      const blankError = app.polyglot.t('settings.storeTab.errors.modIsBlank');
      this.showModByIDError(blankError);
    }
  }

  onClickVerifiedOnly(e) {
    this.showVerifiedOnly = $(e.target).prop('checked');
  }

  getProfileFormData(subset = this.$profileFormFields) {
    return super.getFormData(subset);
  }

  getSettingsData() {
    let selected = app.settings.get('storeModerators');
    // The mods may not have loaded in the interface yet. Subtract only explicitly de-selected ones.
    selected = _.without(selected, ...this.modsSelected.unselectedIDs);
    const byID = this.modsByID.selectedIDs;
    const available = this.modsAvailable.selectedIDs;
    return { storeModerators: [...new Set([...selected, ...byID, ...available])] };
  }

  save() {
    // this view saves to two different models
    const profileFormData = this.getProfileFormData();
    const settingsFormData = this.getSettingsData();

    this.profile.set(profileFormData);
    this.profile.set(profileFormData, { validate: true });
    this.settings.set(settingsFormData);
    this.settings.set(settingsFormData, { validate: true });

    if (!this.profile.validationError && !this.settings.validationError) {
      this.getCachedEl('.js-save').addClass('processing');

      const msg = {
        msg: app.polyglot.t('settings.storeTab.status.saving'),
        type: 'message',
      };

      const statusMessage = app.statusBar.pushMessage({
        ...msg,
        duration: 9999999999999999,
      });

      const profileSave = this.profile.save(profileFormData, {
        attrs: profileFormData,
        type: 'PATCH',
      });

      const settingsSave = this.settings.save(settingsFormData, {
        attrs: settingsFormData,
        type: 'PATCH',
      });

      $.when(profileSave, settingsSave)
        .done(() => {
          // both have saved
          statusMessage.update({
            msg: app.polyglot.t('settings.storeTab.status.done'),
            type: 'confirmed',
          });

          // move the changed moderators
          this.currentMods = this.settings.get('storeModerators');
          const unSel = this.modsSelected.unselectedIDs;
          const remSel = this.modsSelected.removeModeratorsByID(unSel);
          const remByID = this.modsByID.removeModeratorsByID(this.modsByID.selectedIDs);
          const remAvail = this.modsAvailable.removeModeratorsByID(this.modsAvailable.selectedIDs);

          this.modsByID.excludeIDs = this.currentMods;
          this.modsByID.moderatorsStatus.setState({
            hidden: true,
          });

          this.modsSelected.moderatorsCol.add([...remByID, ...remAvail]);
          this.modsSelected.moderatorsStatus.setState({
            hidden: true,
          });

          this.modsAvailable.excludeIDs = this.currentMods;
          this.modsAvailable.moderatorsCol.add(remSel);
          this.modsAvailable.moderatorsStatus.setState({
            hidden: false,
            total: this.modsAvailable.modCount,
            showSpinner: false,
          });

          // If any of the mods moved to the available collect are unverified, show them
          if (app.verifiedMods.matched(unSel).length !== unSel.length) {
            // Don't render, the render is in the always handler
            this._showVerifiedOnly = false;
          }
        })
        .fail((...args) => {
          // if at least one save fails, the save has failed.
          const errMsg = args[0] && args[0].responseJSON &&
            args[0].responseJSON.reason || '';
          openSimpleMessage(app.polyglot.t('settings.storeTab.status.error'), errMsg);

          statusMessage.update({
            msg: app.polyglot.t('settings.storeTab.status.fail'),
            type: 'warning',
          });
        })
        .always(() => {
          this.getCachedEl('.js-save').removeClass('processing');
          setTimeout(() => statusMessage.remove(), 3000);
          this.render();
        });
    } else {
      const $firstErr = this.$('.errorList:first:not(.hide)');

      if ($firstErr.length) {
        $firstErr[0].scrollIntoViewIfNeeded();
      } else {
        const models = [];
        if (this.profile.validationError) models.push(this.profile);
        if (this.settings.validationError) models.push(this.settings);
        this.trigger('unrecognizedModelError', this, models);
      }
    }
  }

  set showVerifiedOnly(bool) {
    this._showVerifiedOnly = bool;
    this.modsAvailable.setState({ showVerifiedOnly: bool });
  }

  render() {
    super.render();

    loadTemplate('modals/settings/store.html', (t) => {
      this.$el.html(t({
        modsAvailable: this.modsAvailable.allIDs,
        showVerifiedOnly: this._showVerifiedOnly,
        errors: {
          ...(this.profile.validationError || {}),
          ...(this.settings.validationError || {}),
        },
        ...this.profile.toJSON(),
        ...this.settings.toJSON(),
      }));

      this.modsSelected.delegateEvents();
      this.$('.js-modListSelected').append(this.modsSelected.render().el);
      if (!this.modsSelected.modFetches.length) {
        this.modsSelected.getModeratorsByID({ moderatorIDs: this.currentMods });
      }

      this.modsByID.delegateEvents();
      this.getCachedEl('.js-modListByID')
        .append(this.modsByID.render().el)
        .toggleClass('hide', !this.modsByID.allIDs.length);

      this.modsAvailable.delegateEvents();
      this.modsAvailable.setState({ showVerifiedOnly: this._showVerifiedOnly });
      this.getCachedEl('.js-modListAvailable')
        .append(this.modsAvailable.render().el)
        .toggleClass('hide', !this.modsAvailable.allIDs.length);
    });

    return this;
  }
}

<commit_msg>Fix wrong class<commit_after>import $ from 'jquery';
import _ from 'underscore';
import app from '../../../app';
import loadTemplate from '../../../utils/loadTemplate';
import '../../../lib/select2';
import '../../../lib/whenAll.jquery';
import { isMultihash } from '../../../utils';
import baseVw from '../../baseVw';
import Moderators from '../../components/Moderators';
import { openSimpleMessage } from '../SimpleMessage';

export default class extends baseVw {
  constructor(options = {}) {
    const opts = {
      className: 'settingsStore',
      ...options,
    };
    super(opts);
    this.options = opts;

    this.profile = app.profile.clone();

    // Sync our clone with any changes made to the global profile.
    this.listenTo(app.profile, 'someChange',
      (md, pOpts) => this.profile.set(pOpts.setAttrs));

    // Sync the global profile with any changes we save via our clone.
    this.listenTo(this.profile, 'sync',
      (md, resp, pOpts) => app.profile.set(this.profile.toJSON(pOpts.attrs)));

    this.settings = app.settings.clone();

    // Sync our clone with any changes made to the global settings model.
    this.listenTo(app.settings, 'someChange',
      (md, sOpts) => this.settings.set(sOpts.setAttrs));

    // Sync the global settings model with any changes we save via our clone.
    this.listenTo(this.settings, 'sync',
      (md, resp, sOpts) => app.settings.set(this.settings.toJSON(sOpts.attrs)));

    this.currentMods = this.settings.get('storeModerators');
    this._showVerifiedOnly = true;

    this.modsSelected = new Moderators({
      fetchErrorTitle: app.polyglot.t('settings.storeTab.errors.selectedModsTitle'),
      cardState: 'selected',
      notSelected: 'deselected',
      showInvalid: true,
      controlsOnInvalid: true,
      showSpinner: false,
    });

    this.modsByID = new Moderators({
      async: false,
      fetchErrorTitle: app.polyglot.t('settings.storeTab.errors.modNotFoundTitle'),
      excludeIDs: this.currentMods,
      showInvalid: true,
      wrapperClasses: 'noMin',
      showSpinner: false,
    });

    this.listenTo(this.modsByID, 'noModsFound', (mOpts) => this.noModsByIDFound(mOpts.guids));

    this.modsAvailable = new Moderators({
      apiPath: 'moderators',
      excludeIDs: this.currentMods,
      showVerifiedOnly: true,
      fetchErrorTitle: app.polyglot.t('settings.storeTab.errors.availableModsTitle'),
      showLoadBtn: true,
    });
  }

  events() {
    return {
      'click .js-browseMods': 'fetchAvailableModerators',
      'click .js-submitModByID': 'clickSubmitModByID',
      'click .js-save': 'save',
      'click .js-storeVerifiedOnly': 'onClickVerifiedOnly',
    };
  }

  noModsByIDFound(guids) {
    const modsNotFound = app.polyglot.t('settings.storeTab.errors.modsNotFound',
      { guids, smart_count: guids.length });
    this.showModByIDError(modsNotFound);
    if (this.modsByID.modCount === 0) {
      this.getCachedEl('.js-modListByID').addClass('hide');
    }
  }

  fetchAvailableModerators() {
    // get the verified mods via POST
    this.modsAvailable.getModeratorsByID({
      moderatorIDs: app.verifiedMods.pluck('peerID'),
      useCache: true,
      method: 'POST',
      apiPath: 'fetchprofiles',
    });
    // get random mods via GET
    this.modsAvailable.getModeratorsByID();
    this.getCachedEl('.js-modListAvailable').removeClass('hide');
    this.getCachedEl('.js-noModsAdded').addClass('hide');
  }

  showModByIDError(msg) {
    this.getCachedEl('.js-submitModByIDInputError').removeClass('hide');
    this.getCachedEl('.js-submitModByIDInputErrorText').text(msg);
  }

  clickSubmitModByID() {
    let modID = this.getCachedEl('.js-submitModByIDInput').val();

    this.getCachedEl('.js-submitModByIDInputError').addClass('hide');

    if (modID) {
      // trim unwanted copy and paste characters
      modID = modID.replace('ob://', '');
      modID = modID.split('/')[0];
      modID = modID.trim();

      if (isMultihash(modID)) {
        if (!this.currentMods.includes(modID)) {
          if (modID !== app.profile.id) {
            this.modsByID.getModeratorsByID({ moderatorIDs: [modID] });
            this.getCachedEl('.js-modListByID').removeClass('hide');
          } else {
            const ownGUID = app.polyglot.t('settings.storeTab.errors.ownGUID', { guid: modID });
            this.showModByIDError(ownGUID);
          }
        } else {
          const dupeGUID = app.polyglot.t('settings.storeTab.errors.dupeGUID', { guid: modID });
          this.showModByIDError(dupeGUID);
        }
      } else {
        const notGUID = app.polyglot.t('settings.storeTab.errors.notGUID', { guid: modID });
        this.showModByIDError(notGUID);
      }
    } else {
      const blankError = app.polyglot.t('settings.storeTab.errors.modIsBlank');
      this.showModByIDError(blankError);
    }
  }

  onClickVerifiedOnly(e) {
    this.showVerifiedOnly = $(e.target).prop('checked');
  }

  getProfileFormData(subset = this.$profileFormFields) {
    return super.getFormData(subset);
  }

  getSettingsData() {
    let selected = app.settings.get('storeModerators');
    // The mods may not have loaded in the interface yet. Subtract only explicitly de-selected ones.
    selected = _.without(selected, ...this.modsSelected.unselectedIDs);
    const byID = this.modsByID.selectedIDs;
    const available = this.modsAvailable.selectedIDs;
    return { storeModerators: [...new Set([...selected, ...byID, ...available])] };
  }

  save() {
    // this view saves to two different models
    const profileFormData = this.getProfileFormData();
    const settingsFormData = this.getSettingsData();

    this.profile.set(profileFormData);
    this.profile.set(profileFormData, { validate: true });
    this.settings.set(settingsFormData);
    this.settings.set(settingsFormData, { validate: true });

    if (!this.profile.validationError && !this.settings.validationError) {
      this.getCachedEl('.js-save').addClass('processing');

      const msg = {
        msg: app.polyglot.t('settings.storeTab.status.saving'),
        type: 'message',
      };

      const statusMessage = app.statusBar.pushMessage({
        ...msg,
        duration: 9999999999999999,
      });

      const profileSave = this.profile.save(profileFormData, {
        attrs: profileFormData,
        type: 'PATCH',
      });

      const settingsSave = this.settings.save(settingsFormData, {
        attrs: settingsFormData,
        type: 'PATCH',
      });

      $.when(profileSave, settingsSave)
        .done(() => {
          // both have saved
          statusMessage.update({
            msg: app.polyglot.t('settings.storeTab.status.done'),
            type: 'confirmed',
          });

          // move the changed moderators
          this.currentMods = this.settings.get('storeModerators');
          const unSel = this.modsSelected.unselectedIDs;
          const remSel = this.modsSelected.removeModeratorsByID(unSel);
          const remByID = this.modsByID.removeModeratorsByID(this.modsByID.selectedIDs);
          const remAvail = this.modsAvailable.removeModeratorsByID(this.modsAvailable.selectedIDs);

          this.modsByID.excludeIDs = this.currentMods;
          this.modsByID.moderatorsStatus.setState({
            hidden: true,
          });

          this.modsSelected.moderatorsCol.add([...remByID, ...remAvail]);
          this.modsSelected.moderatorsStatus.setState({
            hidden: true,
          });

          this.modsAvailable.excludeIDs = this.currentMods;
          this.modsAvailable.moderatorsCol.add(remSel);
          this.modsAvailable.moderatorsStatus.setState({
            hidden: false,
            total: this.modsAvailable.modCount,
            showSpinner: false,
          });

          // If any of the mods moved to the available collect are unverified, show them
          if (app.verifiedMods.matched(unSel).length !== unSel.length) {
            // Don't render, the render is in the always handler
            this._showVerifiedOnly = false;
          }
        })
        .fail((...args) => {
          // if at least one save fails, the save has failed.
          const errMsg = args[0] && args[0].responseJSON &&
            args[0].responseJSON.reason || '';
          openSimpleMessage(app.polyglot.t('settings.storeTab.status.error'), errMsg);

          statusMessage.update({
            msg: app.polyglot.t('settings.storeTab.status.fail'),
            type: 'warning',
          });
        })
        .always(() => {
          this.getCachedEl('.js-save').removeClass('processing');
          setTimeout(() => statusMessage.remove(), 3000);
          this.render();
        });
    } else {
      const $firstErr = this.$('.errorList:first:not(.hide)');

      if ($firstErr.length) {
        $firstErr[0].scrollIntoViewIfNeeded();
      } else {
        const models = [];
        if (this.profile.validationError) models.push(this.profile);
        if (this.settings.validationError) models.push(this.settings);
        this.trigger('unrecognizedModelError', this, models);
      }
    }
  }

  set showVerifiedOnly(bool) {
    this._showVerifiedOnly = bool;
    this.modsAvailable.setState({ showVerifiedOnly: bool });
  }

  render() {
    super.render();

    loadTemplate('modals/settings/store.html', (t) => {
      this.$el.html(t({
        modsAvailable: this.modsAvailable.allIDs,
        showVerifiedOnly: this._showVerifiedOnly,
        errors: {
          ...(this.profile.validationError || {}),
          ...(this.settings.validationError || {}),
        },
        ...this.profile.toJSON(),
        ...this.settings.toJSON(),
      }));

      this.modsSelected.delegateEvents();
      this.$('.js-modListSelected').append(this.modsSelected.render().el);
      if (!this.modsSelected.modFetches.length) {
        this.modsSelected.getModeratorsByID({ moderatorIDs: this.currentMods });
      }

      this.modsByID.delegateEvents();
      this.getCachedEl('.js-modListByID')
        .append(this.modsByID.render().el)
        .toggleClass('hide', !this.modsByID.allIDs.length);

      this.modsAvailable.delegateEvents();
      this.modsAvailable.setState({ showVerifiedOnly: this._showVerifiedOnly });
      this.getCachedEl('.js-modListAvailable')
        .append(this.modsAvailable.render().el)
        .toggleClass('hide', !this.modsAvailable.allIDs.length);
    });

    return this;
  }
}

<|endoftext|>


<commit_before>// Copyright (C) 2012 Oskar Skoog. Released under GPL.

'use strict';

angular.module('jskom.connections', ['jskom.httpkom', 'jskom.services']).
  factory('jskomCacheFactory', [
    '$log', '$cacheFactory',
    function($log, $cacheFactory) {
      // Our $cacheFactory wrapper that allows you to remove keys that
      // doesn't exist.
      return function(cacheId, options) {
        var cache = $cacheFactory(cacheId, options);

        return {
          info: cache.info,
          put: cache.put,
          get: cache.get,
          remove: function(key) {
            if (!_.isUndefined(cache.get(key))) {
              cache.remove(key);
            }
          },
          removeAll: cache.removeAll,
          destroy: cache.destroy
        };
      };
    }
  ]).
  factory('connectionFactory', [
    '$rootScope', '$log', '$q', '$http',
    'httpkom', 'sessionsService', 'jskomCacheFactory', 'httpkomConnectionHeader',
    function($rootScope, $log, $q, $http,
             httpkom, sessionsService, jskomCacheFactory, httpkomConnectionHeader) {
      var HttpkomConnection = function(httpkomServer, id, serverId, httpkomId, session) {
        this._httpkomServer = httpkomServer;
        this.id = id; // our internal id
        this.serverId = serverId;
        this.httpkomId = httpkomId;
        this.session = session;

        this.textsCache = jskomCacheFactory(this.id + '-texts', { capacity: 100 });
        this.membershipsCache = jskomCacheFactory(this.id + '-memberships', { capacity: 100 });

        this._createSessionPromise = null;
        this._pendingRequests = [];
      };

      _.extend(HttpkomConnection.prototype, {
        _addPendingRequest: function(deferred, requireSession, requireLogin) {
          this._pendingRequests.push({
            deferred: deferred,
            requireLogin: requireLogin,
            requireSession: requireSession
          });
        },

        _removePendingRequest: function(deferred) {
          this._pendingRequests = _.filter(this._pendingRequests, function(req) {
            return req.deferred !== deferred
          });
        },

        _findPendingRequest: function(deferred) {
          return _.find(this._pendingRequests, function(req) {
            return req.deferred === deferred
          });
        },

        _hasPendingRequest: function(deferred) {
          return this._findPendingRequest(deferred) ? true : false;
        },

        _cancelAllPendingRequestsRequiringLogin: function() {
          $log.log("connectionFactory - http() - canceling all requests requiring login");
          this._pendingRequests = _.filter(this._pendingRequests, function(req) {
            return !req.requireLogin;
          });
        },

        _cancelAllPendingRequestsRequiringSession: function() {
          this._cancelAllPendingRequestsRequiringLogin();
          $log.log("connectionFactory - http() - canceling all requests requiring session");
          this._pendingRequests = _.filter(this._pendingRequests, function(req) {
            return !req.requireSession;
          });
        },

        _request: function(config, requireSession, requireLogin) {
          // This method issues the $http requests after we have added
          // httpkom headers. It also checks for 401 and 403 responses
          // and resets the connection variables.  We wrap the $http
          // request in our own deferred so we can decide if and when
          // we should resolve/reject it.

          var deferred = $q.defer();
          var promise = deferred.promise;
          var self = this;

          config.headers = config.headers || {};
          if (requireSession) {
            config.headers[httpkomConnectionHeader] = this.httpkomId;
          }

          var request = $http(config);
          this._addPendingRequest(deferred, requireSession, requireLogin);
          request.then(
            function(response) {
              if (self._hasPendingRequest(deferred)) {
                deferred.resolve(response);
                self._removePendingRequest(deferred);
              }
            },
            function(response) {
              if (self._hasPendingRequest(deferred)) {
                deferred.reject(response);
                self._removePendingRequest(deferred);

                if (response.status == 401) {
                  $log.log("connectionFactory - _request() - 401: ") + config.url;
                  self._cancelAllPendingRequestsRequiringLogin();

                  // We are not logged in according to the server, so
                  // reset the person object.
                  if (self.isLoggedIn()) {
                    self.session.person = null;
                    $rootScope.$broadcast('jskom:connection:changed', self);
                  }
                } else if (response.status == 403) {
                  $log.log("connectionFactory - _request() - 403: " + config.url);
                  // Both the httpkomId and session are invalid.
                  self._cancelAllPendingRequestsRequiringSession();

                  if (self.httpkomId || self.session) {
                    self.httpkomId = null;
                    self.session = null;
                    $rootScope.$broadcast('jskom:connection:changed', self);
                  }
                }
              }
            });

          return promise;
        },

        _createSessionAndRetry: function(originalRequest, requireSession, requireLogin) {
          $log.log("connectionFactory - createSessionAndRetry(): " + originalRequest.url);
          var deferred = $q.defer();
          var promise = deferred.promise;
          var self = this;

          if (this._createSessionPromise == null) {
            this._createSessionPromise = sessionsService.createSession(this).then(
              function(response) {
                $log.log("connectionFactory - createSession - success");
                self._createSessionPromise = null;
                return response;
              },
              function(response) {
                $log.log("connectionFactory - createSession - failure");
              self._createSessionPromise = null;
                return $q.reject(response);
              });
          }

          this._createSessionPromise.then(
            function() {
              $log.log("connectionFactory - createSessionPromise - success");
              // createSession succeeded, issue the original request.
              self._request(originalRequest, requireSession, requireLogin).then(
                function(orgResponse) {
                  deferred.resolve(orgResponse);
                },
                function(orgResponse) {
                  deferred.reject(orgResponse);
                });
            },
            function() {
              $log.log("connectionFactory - createSessionPromise - failure");
              // createSession failed
              // todo: what should we really do here?
              deferred.reject({
                data: null,
                status: 403,
                headers: null,
                config: originalRequest
              });
            });

          return promise;
        },

        http: function(config, requireSession, requireLogin) {
          requireSession = requireSession || false;
          requireLogin = requireLogin || false;

          // Prefix url with the httpkom server and server id
          config.url = this.urlFor(config.url, false);

          // Our $http wrapper.
          //
          // 1. Check if there is an outstanding createSession request.
          //
          //   a. There is: Append the request to the createSession
          //      request, so it is issued when the createSession
          //      request succeeds.
          //
          //   b. There is not: Issue the request with a check for
          //      failure status 403. If the requests fails with
          //      status 403, issue a createSession request and append
          //      the original request as a re-try.
          //
          // If a createSession request fails, all appended requests
          // should be rejected with this made up response:
          //
          //   {
          //     data: null,
          //     status: 403,
          //     headers: null,
          //     config: request.config
          //   }

          var self = this;
          if (requireLogin && !this.isLoggedIn()) {
            // Requests that require login will not succeed after
            // createSession either, because the user has to
            // login. Fail them immediately.
            $log.log("connectionFactory - http() - failing request that requires login");
            return $q.reject({
              data: null,
              status: 401,
              headers: null,
              config: config
            });
          } else if (requireSession && !this.isConnected()) {
            $log.log("connectionFactory - http() - retrying request after create session");
            return this._createSessionAndRetry(config, requireSession, requireLogin);
          } else {
            //$log.log("connectionFactory - http() - issuing request");
            return this._request(config, requireSession, requireLogin).then(
              null,
              function(response) {
                if (response.status == 403 && !requireLogin) {
                  return self._createSessionAndRetry(config, requireSession, requireLogin);
                } else {
                  return $q.reject(response);
                }
              });
          }
        },

        clearAllCaches: function() {
          $log.log("connection(id: " + this.id + ") - clearing all caches");
          this.textsCache.removeAll();
          this.membershipsCache.removeAll();
        },

        destroyAllCaches: function() {
          this.textsCache.destroy();
          this.membershipsCache.destroy();
        },

        urlFor: function(path, addHttpkomIdQueryParameter) {
          var url = this._httpkomServer + '/' + this.serverId + path;
          if (addHttpkomIdQueryParameter) {
            var kv = encodeURIComponent(httpkomConnectionHeader) + '=' +
              encodeURIComponent(this.httpkomId);
            if (url.indexOf('?') == -1) {
              url += '?' + kv;
            } else {
              url += '&' + kv;
            }
          }
          return url;
        },

        isConnected: function() {
          return (this.httpkomId && this.session) ? true : false;
        },

        isLoggedIn: function() {
          // We want to explicitly return true or false, because
          // "(this.session && this.session.person)" doesn't return a
          // boolean, but rather null, undefined or true depending on
          // the values. This causes angular to change state in an
          // ng-switch, and re-draw the DOM (which resets the scope).
          return (this.session && this.session.person) ? true : false;
        },

        getPersNo: function() {
          return this.session.person.pers_no;
        },

        toObject: function() {
          return {
            id: this.id,
            httpkomId: this.httpkomId,
            serverId: this.serverId,
            session: this.session
          };
        }
      });

      // TODO: is this unlikely enough to not get duplicates?
      var newId = function() {
        var min = 1;
        var max = 1000000000;
        return "conn-" + (Math.floor(Math.random() * (max - min + 1)) + min);
      };

      return {
        createConnection: function(obj) {
          obj = obj || {};
          obj.id = obj.id || newId();
          obj.serverId = obj.serverId || null;
          obj.httpkomId = obj.httpkomId || null;
          obj.session = obj.session || null;
          return new HttpkomConnection(httpkom.getHttpkomServer(),
                                       obj.id, obj.serverId, obj.httpkomId, obj.session);
        }
      };
    }
  ]).
  factory('connectionsStorage', [
    '$log', 'connectionFactory',
    function($log, connectionFactory) {
      return {
        saveCurrentConnectionId: function(id) {
          localStorage.setItem("currentConnectionId", id);
        },

        loadCurrentConnectionId: function() {
          return localStorage.getItem("currentConnectionId");
        },

        loadConnections: function() {
          // If there is list of connections in localStorage, create one.
          var connections = angular.fromJson(localStorage.getItem("connections"));
          connections = connections || {};
          _.each(connections, function(connObj, id) {
            connections[id] = connectionFactory.createConnection(connObj);
          });
          return connections;
        },

        saveConnections: function(connections) {
          var objs = {};
          _.each(connections, function(conn, id) {
            objs[id] = conn.toObject();
          });
          localStorage.setItem("connections", angular.toJson(objs));
        },
      };
    }
  ]).
  factory('connectionsService', [
    '$rootScope', '$log', '$q',
    'httpkom', 'connectionsStorage', 'connectionFactory', 'sessionsService',
    function($rootScope, $log, $q,
             httpkom, connectionsStorage, connectionFactory, sessionsService) {
      var connections = connectionsStorage.loadConnections();
      var currentConnectionId = connectionsStorage.loadCurrentConnectionId();

      var saveCurrentId = function() {
        connectionsStorage.saveCurrentConnectionId(currentConnectionId);
      };

      var saveConnections = function() {
        connectionsStorage.saveConnections(connections);
      };

      var addConnection = function(connection) {
        connections[connection.id] = connection;
        saveConnections();
      };

      var setCurrentConnection = function(connection) {
        if (connection) {
          if (!connection[connection.id]) {
            addConnection(connection);
          }
          currentConnectionId = connection.id;
        } else {
          currentConnectionId = null;
        }
        saveCurrentId();
        pruneInactiveConnections();
      };

      var removeConnection = function(connection) {
        if (currentConnectionId == connection.id) {
          currentConnectionId = null;
          saveCurrentId();
        }
        if (connections[connection.id]) {
          connections[connection.id].destroyAllCaches();
          delete connections[connection.id];
        }
        saveConnections();
        if (currentConnectionId == null) {
          var firstConn = _.first(_.values(connections));
          if (firstConn) {
            setCurrentConnection(firstConn);
          } else {
            createNewConnectionPromise().then(
              function(newConn) {
                setCurrentConnection(newConn);
              });
          }
        }
      };

      var serversPromise = httpkom.getLyskomServers().then(
        function(response) {
          $log.log("connectionsService - getServers() - success");
          return response.data;
        },
        function(response) {
          $log.log("connectionsService - getServers() - error");
            return $q.reject(response);
        });

      var createNewConnectionPromise = function() {
        var deferred = $q.defer();
        var promise = deferred.promise;
        serversPromise.then(
          function(servers) {
            var firstServer = _.first(_.values(servers));
            var serverId = null;
            if (firstServer) {
              serverId = firstServer.id;
            }
            var conn = connectionFactory.createConnection({ serverId: serverId });
            deferred.resolve(conn);
          },
          function(response) {
            deferred.reject(response);
          });
        return promise;
      };

      var pruneInactiveConnections = function() {
        _.each(connections, function(conn) {
          // Only check connections that are not the current one
          if (conn.id != currentConnectionId && !conn.isLoggedIn()) {
            $log.log("connectionsService - removing inactive connection: " + conn.id);
            if (conn.isConnected()) {
              sessionsService.deleteSession(conn, 0).then(
                function() {
                    removeConnection(conn);
                });
            } else {
              removeConnection(conn);
            }
          }
        });
      };

      $rootScope.$on('jskom:connection:changed', function($event, connection) {
        $log.log('connectionsService - on(jskom:connection:changed)');
        saveConnections();
      });

      return {
        getServers: function() {
          return serversPromise;
        },

        newConnectionPromise: function() {
          return createNewConnectionPromise();
        },

        getCurrentConnection: function() {
          return connections[currentConnectionId];
        },

        setCurrentConnection: function(connection) {
          setCurrentConnection(connection);
        },

        addConnection: function(connection) {
          addConnection(connection);
        },

        removeConnection: function(connection) {
          removeConnection(connection);
        },

        getConnections: function() {
          return connections;
        },
      };
    }
  ]);
<commit_msg>Added pruning of invalid connections (connections to lyskom servers that no longer exists on the httpkom server).<commit_after>// Copyright (C) 2012 Oskar Skoog. Released under GPL.

'use strict';

angular.module('jskom.connections', ['jskom.httpkom', 'jskom.services']).
  factory('jskomCacheFactory', [
    '$log', '$cacheFactory',
    function($log, $cacheFactory) {
      // Our $cacheFactory wrapper that allows you to remove keys that
      // doesn't exist.
      return function(cacheId, options) {
        var cache = $cacheFactory(cacheId, options);

        return {
          info: cache.info,
          put: cache.put,
          get: cache.get,
          remove: function(key) {
            if (!_.isUndefined(cache.get(key))) {
              cache.remove(key);
            }
          },
          removeAll: cache.removeAll,
          destroy: cache.destroy
        };
      };
    }
  ]).
  factory('connectionFactory', [
    '$rootScope', '$log', '$q', '$http',
    'httpkom', 'sessionsService', 'jskomCacheFactory', 'httpkomConnectionHeader',
    function($rootScope, $log, $q, $http,
             httpkom, sessionsService, jskomCacheFactory, httpkomConnectionHeader) {
      var HttpkomConnection = function(httpkomServer, id, serverId, httpkomId, session) {
        this._httpkomServer = httpkomServer;
        this.id = id; // our internal id
        this.serverId = serverId;
        this.httpkomId = httpkomId;
        this.session = session;

        this.textsCache = jskomCacheFactory(this.id + '-texts', { capacity: 100 });
        this.membershipsCache = jskomCacheFactory(this.id + '-memberships', { capacity: 100 });

        this._createSessionPromise = null;
        this._pendingRequests = [];
      };

      _.extend(HttpkomConnection.prototype, {
        _addPendingRequest: function(deferred, requireSession, requireLogin) {
          this._pendingRequests.push({
            deferred: deferred,
            requireLogin: requireLogin,
            requireSession: requireSession
          });
        },

        _removePendingRequest: function(deferred) {
          this._pendingRequests = _.filter(this._pendingRequests, function(req) {
            return req.deferred !== deferred
          });
        },

        _findPendingRequest: function(deferred) {
          return _.find(this._pendingRequests, function(req) {
            return req.deferred === deferred
          });
        },

        _hasPendingRequest: function(deferred) {
          return this._findPendingRequest(deferred) ? true : false;
        },

        _cancelAllPendingRequestsRequiringLogin: function() {
          $log.log("connectionFactory - http() - canceling all requests requiring login");
          this._pendingRequests = _.filter(this._pendingRequests, function(req) {
            return !req.requireLogin;
          });
        },

        _cancelAllPendingRequestsRequiringSession: function() {
          this._cancelAllPendingRequestsRequiringLogin();
          $log.log("connectionFactory - http() - canceling all requests requiring session");
          this._pendingRequests = _.filter(this._pendingRequests, function(req) {
            return !req.requireSession;
          });
        },

        _request: function(config, requireSession, requireLogin) {
          // This method issues the $http requests after we have added
          // httpkom headers. It also checks for 401 and 403 responses
          // and resets the connection variables.  We wrap the $http
          // request in our own deferred so we can decide if and when
          // we should resolve/reject it.

          var deferred = $q.defer();
          var promise = deferred.promise;
          var self = this;

          config.headers = config.headers || {};
          if (requireSession) {
            config.headers[httpkomConnectionHeader] = this.httpkomId;
          }

          var request = $http(config);
          this._addPendingRequest(deferred, requireSession, requireLogin);
          request.then(
            function(response) {
              if (self._hasPendingRequest(deferred)) {
                deferred.resolve(response);
                self._removePendingRequest(deferred);
              }
            },
            function(response) {
              if (self._hasPendingRequest(deferred)) {
                deferred.reject(response);
                self._removePendingRequest(deferred);

                if (response.status == 401) {
                  $log.log("connectionFactory - _request() - 401: ") + config.url;
                  self._cancelAllPendingRequestsRequiringLogin();

                  // We are not logged in according to the server, so
                  // reset the person object.
                  if (self.isLoggedIn()) {
                    self.session.person = null;
                    $rootScope.$broadcast('jskom:connection:changed', self);
                  }
                } else if (response.status == 403) {
                  $log.log("connectionFactory - _request() - 403: " + config.url);
                  // Both the httpkomId and session are invalid.
                  self._cancelAllPendingRequestsRequiringSession();

                  if (self.httpkomId || self.session) {
                    self.httpkomId = null;
                    self.session = null;
                    $rootScope.$broadcast('jskom:connection:changed', self);
                  }
                }
              }
            });

          return promise;
        },

        _createSessionAndRetry: function(originalRequest, requireSession, requireLogin) {
          $log.log("connectionFactory - createSessionAndRetry(): " + originalRequest.url);
          var deferred = $q.defer();
          var promise = deferred.promise;
          var self = this;

          if (this._createSessionPromise == null) {
            this._createSessionPromise = sessionsService.createSession(this).then(
              function(response) {
                $log.log("connectionFactory - createSession - success");
                self._createSessionPromise = null;
                return response;
              },
              function(response) {
                $log.log("connectionFactory - createSession - failure");
              self._createSessionPromise = null;
                return $q.reject(response);
              });
          }

          this._createSessionPromise.then(
            function() {
              $log.log("connectionFactory - createSessionPromise - success");
              // createSession succeeded, issue the original request.
              self._request(originalRequest, requireSession, requireLogin).then(
                function(orgResponse) {
                  deferred.resolve(orgResponse);
                },
                function(orgResponse) {
                  deferred.reject(orgResponse);
                });
            },
            function() {
              $log.log("connectionFactory - createSessionPromise - failure");
              // createSession failed
              // todo: what should we really do here?
              deferred.reject({
                data: null,
                status: 403,
                headers: null,
                config: originalRequest
              });
            });

          return promise;
        },

        http: function(config, requireSession, requireLogin) {
          requireSession = requireSession || false;
          requireLogin = requireLogin || false;

          // Prefix url with the httpkom server and server id
          config.url = this.urlFor(config.url, false);

          // Our $http wrapper.
          //
          // 1. Check if there is an outstanding createSession request.
          //
          //   a. There is: Append the request to the createSession
          //      request, so it is issued when the createSession
          //      request succeeds.
          //
          //   b. There is not: Issue the request with a check for
          //      failure status 403. If the requests fails with
          //      status 403, issue a createSession request and append
          //      the original request as a re-try.
          //
          // If a createSession request fails, all appended requests
          // should be rejected with this made up response:
          //
          //   {
          //     data: null,
          //     status: 403,
          //     headers: null,
          //     config: request.config
          //   }

          var self = this;
          if (requireLogin && !this.isLoggedIn()) {
            // Requests that require login will not succeed after
            // createSession either, because the user has to
            // login. Fail them immediately.
            $log.log("connectionFactory - http() - failing request that requires login");
            return $q.reject({
              data: null,
              status: 401,
              headers: null,
              config: config
            });
          } else if (requireSession && !this.isConnected()) {
            $log.log("connectionFactory - http() - retrying request after create session");
            return this._createSessionAndRetry(config, requireSession, requireLogin);
          } else {
            //$log.log("connectionFactory - http() - issuing request");
            return this._request(config, requireSession, requireLogin).then(
              null,
              function(response) {
                if (response.status == 403 && !requireLogin) {
                  return self._createSessionAndRetry(config, requireSession, requireLogin);
                } else {
                  return $q.reject(response);
                }
              });
          }
        },

        clearAllCaches: function() {
          $log.log("connection(id: " + this.id + ") - clearing all caches");
          this.textsCache.removeAll();
          this.membershipsCache.removeAll();
        },

        destroyAllCaches: function() {
          this.textsCache.destroy();
          this.membershipsCache.destroy();
        },

        urlFor: function(path, addHttpkomIdQueryParameter) {
          var url = this._httpkomServer + '/' + this.serverId + path;
          if (addHttpkomIdQueryParameter) {
            var kv = encodeURIComponent(httpkomConnectionHeader) + '=' +
              encodeURIComponent(this.httpkomId);
            if (url.indexOf('?') == -1) {
              url += '?' + kv;
            } else {
              url += '&' + kv;
            }
          }
          return url;
        },

        isConnected: function() {
          return (this.httpkomId && this.session) ? true : false;
        },

        isLoggedIn: function() {
          // We want to explicitly return true or false, because
          // "(this.session && this.session.person)" doesn't return a
          // boolean, but rather null, undefined or true depending on
          // the values. This causes angular to change state in an
          // ng-switch, and re-draw the DOM (which resets the scope).
          return (this.session && this.session.person) ? true : false;
        },

        getPersNo: function() {
          return this.session.person.pers_no;
        },

        toObject: function() {
          return {
            id: this.id,
            httpkomId: this.httpkomId,
            serverId: this.serverId,
            session: this.session
          };
        }
      });

      // TODO: is this unlikely enough to not get duplicates?
      var newId = function() {
        var min = 1;
        var max = 1000000000;
        return "conn-" + (Math.floor(Math.random() * (max - min + 1)) + min);
      };

      return {
        createConnection: function(obj) {
          obj = obj || {};
          obj.id = obj.id || newId();
          obj.serverId = obj.serverId || null;
          obj.httpkomId = obj.httpkomId || null;
          obj.session = obj.session || null;
          return new HttpkomConnection(httpkom.getHttpkomServer(),
                                       obj.id, obj.serverId, obj.httpkomId, obj.session);
        }
      };
    }
  ]).
  factory('connectionsStorage', [
    '$log', 'connectionFactory',
    function($log, connectionFactory) {
      return {
        saveCurrentConnectionId: function(id) {
          localStorage.setItem("currentConnectionId", id);
        },

        loadCurrentConnectionId: function() {
          return localStorage.getItem("currentConnectionId");
        },

        loadConnections: function() {
          // If there is list of connections in localStorage, create one.
          var connections = angular.fromJson(localStorage.getItem("connections"));
          connections = connections || {};
          _.each(connections, function(connObj, id) {
            connections[id] = connectionFactory.createConnection(connObj);
          });
          return connections;
        },

        saveConnections: function(connections) {
          var objs = {};
          _.each(connections, function(conn, id) {
            objs[id] = conn.toObject();
          });
          localStorage.setItem("connections", angular.toJson(objs));
        },
      };
    }
  ]).
  factory('connectionsService', [
    '$rootScope', '$log', '$q',
    'httpkom', 'connectionsStorage', 'connectionFactory', 'sessionsService',
    function($rootScope, $log, $q,
             httpkom, connectionsStorage, connectionFactory, sessionsService) {
      var connections = connectionsStorage.loadConnections();
      var currentConnectionId = connectionsStorage.loadCurrentConnectionId();

      var saveCurrentId = function() {
        connectionsStorage.saveCurrentConnectionId(currentConnectionId);
      };

      var saveConnections = function() {
        connectionsStorage.saveConnections(connections);
      };

      var addConnection = function(connection) {
        connections[connection.id] = connection;
        saveConnections();
      };

      var setCurrentConnection = function(connection) {
        if (connection) {
          if (!connection[connection.id]) {
            addConnection(connection);
          }
          currentConnectionId = connection.id;
        } else {
          currentConnectionId = null;
        }
        saveCurrentId();
        pruneInactiveConnections();
      };

      var removeConnection = function(connection) {
        if (currentConnectionId == connection.id) {
          currentConnectionId = null;
          saveCurrentId();
        }
        if (connections[connection.id]) {
          connections[connection.id].destroyAllCaches();
          delete connections[connection.id];
        }
        saveConnections();
        if (currentConnectionId == null) {
          var firstConn = _.first(_.values(connections));
          if (firstConn) {
            setCurrentConnection(firstConn);
          } else {
            createNewConnectionPromise().then(
              function(newConn) {
                setCurrentConnection(newConn);
              });
          }
        }
      };

      var pruneInvalidConnections = function(servers) {
        // Remove connections to lyskom servers that no longer exist
        // on httpkom.
        _.each(connections, function(conn) {
          // Remove connections to servers that don't exist, because
          // we can't do anything with them.
          if (!_.has(servers, conn.serverId)) {
            $log.log("connectionsService - removing invalid connection (unknown serverId: " +
                     conn.serverId + "): " + conn.id);
            removeConnection(conn);
          }
        });
      };

      var serversPromise = httpkom.getLyskomServers().then(
        function(response) {
          $log.log("connectionsService - getServers() - success");
          pruneInvalidConnections(response.data);
          pruneInactiveConnections();
          return response.data;
        },
        function(response) {
          $log.log("connectionsService - getServers() - error");
            return $q.reject(response);
        });

      var createNewConnectionPromise = function() {
        var deferred = $q.defer();
        var promise = deferred.promise;
        serversPromise.then(
          function(servers) {
            var firstServer = _.first(_.values(servers));
            var serverId = null;
            if (firstServer) {
              serverId = firstServer.id;
            }
            var conn = connectionFactory.createConnection({ serverId: serverId });
            deferred.resolve(conn);
          },
          function(response) {
            deferred.reject(response);
          });
        return promise;
      };

      var pruneInactiveConnections = function() {
        _.each(connections, function(conn) {
          // Only check connections that are not the current one
          if (conn.id != currentConnectionId && !conn.isLoggedIn()) {
            $log.log("connectionsService - removing inactive connection: " + conn.id);
            if (conn.isConnected()) {
              sessionsService.deleteSession(conn, 0).then(
                function() {
                    removeConnection(conn);
                });
            } else {
              removeConnection(conn);
            }
          }
        });
      };

      $rootScope.$on('jskom:connection:changed', function($event, connection) {
        $log.log('connectionsService - on(jskom:connection:changed)');
        saveConnections();
      });

      return {
        getServers: function() {
          return serversPromise;
        },

        newConnectionPromise: function() {
          return createNewConnectionPromise();
        },

        getCurrentConnection: function() {
          return connections[currentConnectionId];
        },

        setCurrentConnection: function(connection) {
          setCurrentConnection(connection);
        },

        addConnection: function(connection) {
          addConnection(connection);
        },

        removeConnection: function(connection) {
          removeConnection(connection);
        },

        getConnections: function() {
          return connections;
        },
      };
    }
  ]);
<|endoftext|>


<commit_before>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var EventEmitter = require( 'events' ).EventEmitter;
var logger = require( 'debug' );
var getKeys = require( 'object-keys' ).shim();
var inherit = require( '@stdlib/utils/inherit' );
var copy = require( '@stdlib/utils/copy' );
var mergeFcn = require( '@stdlib/utils/merge' ).factory;
var setReadOnly = require( '@stdlib/utils/define-read-only-property' );
var isObject = require( '@stdlib/assert/is-plain-object' );
var isNumber = require( '@stdlib/assert/is-number' ).isPrimitive;
var minstd = require( '@stdlib/random/base/minstd' );
var defaults = require( './defaults.js' );

// VARIABLES //

var debug = logger( 'hist:main' );
var PRIVATE_PROPS = [
	'_autoRender',
	'_autoView',
	'_barColor',
	'_barOpacity',
	'_binCounts',
	'_binMax',
	'_binMethod',
	'_binMin',
	'_binWidth',
	'_description',
	'_edges',
	'_engine',
	'_height',
	'_labels',
	'_lineColor',
	'_lineOpacity',
	'_lineStyle',
	'_lineWidth',
	'_nbins',
	'_normalization',
	'_orientation',
	'_paddingBottom',
	'_paddingLeft',
	'_paddingRight',
	'_paddingTop',
	'_renderFormat',
	'_title',
	'_viewer',
	'_width',
	'_xAxisOrient',
	'_xData',
	'_xLabel',
	'_xMax',
	'_xMin',
	'_xNumTicks',
	'_xScale',
	'_xTickFormat',
	'_yAxisOrient',
	'_yData',
	'_yLabel',
	'_yMax',
	'_yMin',
	'_yNumTicks',
	'_yScale',
	'_yTickFormat'
];

// FUNCTIONS //

var merge = mergeFcn({
	'extend': false
});

// MAIN //

/**
* Histogram constructor.
*
* @constructor
* @returns {Histogram} Histogram instance
*
* @example
* TODO
*/
function Histogram() {
	var options;
	var nargs;
	var keys;
	var self;
	var opts;
	var key;
	var i;

	nargs = arguments.length;
	if ( !(this instanceof Histogram) ) {
		if ( nargs === 0 ) {
			return new Histogram();
		}
		if ( nargs === 1 ) {
			return new Histogram( arguments[0] );
		}
		if ( nargs === 2 ) {
			return new Histogram( arguments[0], arguments[1] );
		}
		return new Histogram( arguments[0], arguments[1], arguments[2] );
	}
	self = this;

	opts = defaults();
	if ( nargs === 0 ) {
		options = {};
	} else if ( nargs === 1 ) {
		options = arguments[ 0 ];
		if ( !isObject( options ) ) {
			throw new TypeError( 'invalid input argument. Options argument must be an `object`. Value: `' + options + '`.' );
		}
	} else if ( nargs === 2 ) {
		options = {};
		options.x = arguments[ 0 ];
		if ( isNumber( arguments[ 1 ] ) ) {
			options.nbins = arguments[ 1 ];
		} else {
			options.edges = arguments[ 1 ];
		}
	} else if ( nargs > 2 ) {
		if ( !isObject( arguments[2] ) ) {
			throw new TypeError( 'invalid input argument. Options argument must be an `object`. Value: `' + arguments[2] + '`.' );
		}
		options = copy( arguments[2] ); // avoid mutation
		if ( isNumber( arguments[ 1 ] ) ) {
			options.nbins = arguments[ 1 ];
		} else {
			options.edges = arguments[ 1 ];
		}
	}
	opts = merge( opts, options );

	debug( 'Creating an instance with the following configuration: %s.', JSON.stringify( opts ) );
	EventEmitter.call( this );

	for ( i = 0; i < PRIVATE_PROPS.length; i++ ) {
		Object.defineProperty( this, PRIVATE_PROPS[i], {
			'configurable': false,
			'enumerable': false,
			'writable': true,
			'value': null
		});
	}
	// Set a clipping path id:
	Object.defineProperty( this, '_clipPathId', {
		'configurable': false,
		'enumerable': false,
		'writable': false,
		'value': minstd().toString() // TODO: uuid
	});

	// Initialize an internal cache for renderers...
	Object.defineProperty( this, '$', {
		'configurable': false,
		'enumerable': false,
		'writable': false,
		'value': {}
	});
	Object.defineProperty( this.$, 'svg', {
		'configurable': false,
		'enumerable': false,
		'writable': false,
		'value': {}
	});

	// Set options...
	keys = getKeys( opts );
	for ( i = 0; i < keys.length; i++ ) {
		key = keys[ i ];
		this[ key ] = opts[ key ];
	}

	// Add event listeners:
	this.on( 'change', onChange );
	this.on( 'render', onRender );

	return this;

	/**
	* Callback invoked upon receiving a change event.
	*
	* @private
	*/
	function onChange() {
		/* eslint-disable no-underscore-dangle */
		debug( 'Received a change event.' );
		if ( self._autoRender ) {
			self.render();
		}
	}

	/**
	* Callback invoked upon receiving a render event.
	*
	* @private
	* @param {*} hist - rendered histogram
	*/
	function onRender( hist ) {
		/* eslint-disable no-underscore-dangle */
		debug( 'Received a render event.' );
		if ( self._autoView ) {
			debug( 'Viewer: %s.', self._viewer );
			debug( 'Generating view...' );
			view( self, self._viewer, hist );
		}
	}
}

/*
* Inherit from the `EventEmitter` prototype.
*/
inherit( Histogram, EventEmitter );

// EXPORTS //

module.exports = Histogram;
<commit_msg>Refactor argument handling to assume an "edges" array<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var EventEmitter = require( 'events' ).EventEmitter;
var logger = require( 'debug' );
var getKeys = require( 'object-keys' ).shim();
var inherit = require( '@stdlib/utils/inherit' );
var copy = require( '@stdlib/utils/copy' );
var mergeFcn = require( '@stdlib/utils/merge' ).factory;
var setReadOnly = require( '@stdlib/utils/define-read-only-property' );
var isObject = require( '@stdlib/assert/is-plain-object' );
var minstd = require( '@stdlib/random/base/minstd' );
var defaults = require( './defaults.js' );

// VARIABLES //

var debug = logger( 'hist:main' );
var PRIVATE_PROPS = [
	'_autoRender',
	'_autoView',
	'_barColor',
	'_barOpacity',
	'_binCounts',
	'_binMax',
	'_binMethod',
	'_binMin',
	'_binWidth',
	'_description',
	'_edges',
	'_engine',
	'_height',
	'_labels',
	'_lineColor',
	'_lineOpacity',
	'_lineStyle',
	'_lineWidth',
	'_nbins',
	'_normalization',
	'_orientation',
	'_paddingBottom',
	'_paddingLeft',
	'_paddingRight',
	'_paddingTop',
	'_renderFormat',
	'_title',
	'_viewer',
	'_width',
	'_xAxisOrient',
	'_xData',
	'_xLabel',
	'_xMax',
	'_xMin',
	'_xNumTicks',
	'_xScale',
	'_xTickFormat',
	'_yAxisOrient',
	'_yData',
	'_yLabel',
	'_yMax',
	'_yMin',
	'_yNumTicks',
	'_yScale',
	'_yTickFormat'
];

// FUNCTIONS //

var merge = mergeFcn({
	'extend': false
});

// MAIN //

/**
* Histogram constructor.
*
* @constructor
* @param {Array} [x] - values to bin
* @param {Array} [edges] - edges
* @param {Options} [options] - constructor options
* @returns {Histogram} Histogram instance
*
* @example
* TODO
*/
function Histogram() {
	var options;
	var nargs;
	var keys;
	var self;
	var opts;
	var key;
	var i;

	nargs = arguments.length;
	if ( !(this instanceof Histogram) ) {
		if ( nargs === 0 ) {
			return new Histogram();
		}
		if ( nargs === 1 ) {
			return new Histogram( arguments[0] );
		}
		if ( nargs === 2 ) {
			return new Histogram( arguments[0], arguments[1] );
		}
		return new Histogram( arguments[0], arguments[1], arguments[2] );
	}
	self = this;

	opts = defaults();
	if ( nargs === 0 ) {
		options = {};
	} else if ( nargs === 1 ) {
		options = arguments[ 0 ];
		if ( !isObject( options ) ) {
			throw new TypeError( 'invalid input argument. Options argument must be an `object`. Value: `' + options + '`.' );
		}
	} else if ( nargs === 2 ) {
		options = {};
		options.x = arguments[ 0 ];
		options.edges = arguments[ 1 ];
	} else if ( nargs > 2 ) {
		if ( !isObject( arguments[ 2 ] ) ) {
			throw new TypeError( 'invalid input argument. Options argument must be an `object`. Value: `' + arguments[2] + '`.' );
		}
		options = copy( arguments[ 2 ] ); // avoid mutation
		options.x = arguments[ 0 ];
		options.edges = arguments[ 1 ];
	}
	opts = merge( opts, options );

	debug( 'Creating an instance with the following configuration: %s.', JSON.stringify( opts ) );
	EventEmitter.call( this );

	for ( i = 0; i < PRIVATE_PROPS.length; i++ ) {
		Object.defineProperty( this, PRIVATE_PROPS[i], {
			'configurable': false,
			'enumerable': false,
			'writable': true,
			'value': null
		});
	}
	// Set a clipping path id:
	Object.defineProperty( this, '_clipPathId', {
		'configurable': false,
		'enumerable': false,
		'writable': false,
		'value': minstd().toString() // TODO: uuid
	});

	// Initialize an internal cache for renderers...
	Object.defineProperty( this, '$', {
		'configurable': false,
		'enumerable': false,
		'writable': false,
		'value': {}
	});
	Object.defineProperty( this.$, 'svg', {
		'configurable': false,
		'enumerable': false,
		'writable': false,
		'value': {}
	});

	// Set options...
	keys = getKeys( opts );
	for ( i = 0; i < keys.length; i++ ) {
		key = keys[ i ];
		this[ key ] = opts[ key ];
	}

	// Add event listeners:
	this.on( 'change', onChange );
	this.on( 'render', onRender );

	return this;

	/**
	* Callback invoked upon receiving a change event.
	*
	* @private
	*/
	function onChange() {
		/* eslint-disable no-underscore-dangle */
		debug( 'Received a change event.' );
		if ( self._autoRender ) {
			self.render();
		}
	}

	/**
	* Callback invoked upon receiving a render event.
	*
	* @private
	* @param {*} hist - rendered histogram
	*/
	function onRender( hist ) {
		/* eslint-disable no-underscore-dangle */
		debug( 'Received a render event.' );
		if ( self._autoView ) {
			debug( 'Viewer: %s.', self._viewer );
			debug( 'Generating view...' );
			view( self, self._viewer, hist );
		}
	}
}

/*
* Inherit from the `EventEmitter` prototype.
*/
inherit( Histogram, EventEmitter );

// EXPORTS //

module.exports = Histogram;
<|endoftext|>


<commit_before>/* eslint-disable no-sync */
'use strict';

// MODULES //

var debug = require( 'debug' )( 'repl:build' );
var writeFile = require( 'fs' ).writeFileSync;
var resolvePath = require( 'path' ).resolve;
var join = require( 'path' ).join;
var resolve = require( 'resolve' ).sync;
var readFile = require( '@stdlib/fs/read-file' ).sync;
var readJSON = require( '@stdlib/fs/read-json' ).sync;
var resolveParentPath = require( '@stdlib/fs/resolve-parent-path' ).sync;
var dirname = require( '@stdlib/utils/dirname' );
var replace = require( '@stdlib/string/replace' );
var NAMESPACE = require( './../lib/namespace' );

// VARIABLES //

// Filepath convention for REPL text:
var REPL_TEXT = 'docs/repl.txt';

// Root search directory:
var ROOT = resolvePath( __dirname, '../../' );

// Output file path:
var OUTPUT = resolvePath( __dirname, '../lib/docs.js' );

// Output file preamble:
var PREAMBLE = '// This file is generated by scripts/build.js.\n\'use strict\';\n\n/* eslint-disable quotes */\n\n';

// Identifier for inserting 'See Also' links:
var SEE_ALSO = '\n    See Also\n    --------\n';

// FUNCTIONS //

/**
* Generates hash for REPL help text.
*
* @private
*/
function createHelp() {
	var fpath;
	var alias;
	var ropts;
	var fopts;
	var file;
	var pkg;
	var out;
	var i;

	ropts = {
		'basedir': ROOT
	};
	debug( 'Package resolve options: %s', JSON.stringify( ropts ) );

	fopts = {
		'encoding': 'utf8'
	};

	out = {};
	for ( i = 0; i < NAMESPACE.length; i++ ) {
		alias = NAMESPACE[ i ].alias;
		debug( 'Resolving `%s`', alias );

		fpath = resolve( NAMESPACE[ i ].path, ropts );
		debug( 'Resolved module path: %s', fpath );

		debug( 'Resolving package information.' );
		fpath = resolveParentPath( 'package.json', {
			'dir': dirname( fpath )
		});
		if ( fpath === null ) {
			debug( 'Unable to resolve package information.' );
			continue;
		}
		debug( 'Reading package information.' );
		pkg = readJSON( fpath, fopts );
		if ( pkg instanceof Error ) {
			debug( 'Unable to read package information: %s', pkg.message );
			continue;
		}
		debug( 'Checking package information.' );
		if ( pkg.name !== NAMESPACE[ i ].path ) {
			debug( 'Package information does not match path. Expected: %s. Actual: %s.', NAMESPACE[ i ].path, pkg.name );
			continue;
		}
		fpath = join( dirname( fpath ), REPL_TEXT );
		debug( 'Attempting to read REPL text: %s', fpath );

		file = readFile( fpath, fopts );
		if ( file instanceof Error ) {
			debug( 'Unable to read REPL text.' );
			continue;
		}
		debug( 'Successfully read REPL text.' );

		debug( 'Processing REPL text.' );
		file = replace( file, '{{alias}}', alias );
		if ( NAMESPACE[ i ].related.length ) {
			file = replace( file, SEE_ALSO, SEE_ALSO+'    '+NAMESPACE[i].related.join( ', ' ) );
		} else {
			file = replace( file, SEE_ALSO, '' );
		}
		debug( 'Successfully processed `%s`.', alias );
		out[ alias ] = file;
	}
	debug( 'Writing REPL text hash to file.' );
	out = PREAMBLE+'module.exports = '+JSON.stringify( out, null, '\t' )+';\n';
	writeFile( OUTPUT, out, fopts );
} // end FUNCTION createHelp()

/**
* Main execution sequence.
*
* @private
*/
function main() {
	debug( 'Generating REPL help documentation.' );
	createHelp();
} // end FUNCTION main()

// MAIN //

main();
<commit_msg>Refactor to resolve related aliases based on identifier<commit_after>/* eslint-disable no-sync */
'use strict';

// MODULES //

var debug = require( 'debug' )( 'repl:build' );
var writeFile = require( 'fs' ).writeFileSync;
var resolvePath = require( 'path' ).resolve;
var join = require( 'path' ).join;
var resolve = require( 'resolve' ).sync;
var readFile = require( '@stdlib/fs/read-file' ).sync;
var readJSON = require( '@stdlib/fs/read-json' ).sync;
var resolveParentPath = require( '@stdlib/fs/resolve-parent-path' ).sync;
var dirname = require( '@stdlib/utils/dirname' );
var replace = require( '@stdlib/string/replace' );
var NAMESPACE = require( './../lib/namespace' );

// VARIABLES //

// Filepath convention for REPL text:
var REPL_TEXT = 'docs/repl.txt';

// Root search directory:
var ROOT = resolvePath( __dirname, '../../' );

// Output file path:
var OUTPUT = resolvePath( __dirname, '../lib/docs.js' );

// Output file preamble:
var PREAMBLE = '// This file is generated by scripts/build.js.\n\'use strict\';\n\n/* eslint-disable quotes */\n\n';

// Identifier for inserting 'See Also' links:
var SEE_ALSO = '\n    See Also\n    --------\n';

// FUNCTIONS //

/**
* Generates a hash mapping paths to aliases.
*
* @private
* @returns {Object} hash mapping paths to aliases
*/
function aliasMap() {
	var pkg;
	var out;
	var i;

	out = {};
	for ( i = 0; i < NAMESPACE.length; i++ ) {
		pkg = NAMESPACE[ i ];
		out[ pkg.path ] = pkg.alias;
	}
	return out;
} // end FUNCTION aliasMap()

/**
* Generates hash for REPL help text.
*
* @private
*/
function createHelp() {
	var related;
	var aliases;
	var fpath;
	var alias;
	var ropts;
	var fopts;
	var file;
	var pkg;
	var out;
	var len;
	var tmp;
	var a;
	var i;
	var j;

	ropts = {
		'basedir': ROOT
	};
	debug( 'Package resolve options: %s', JSON.stringify( ropts ) );

	fopts = {
		'encoding': 'utf8'
	};

	aliases = aliasMap();

	out = {};
	for ( i = 0; i < NAMESPACE.length; i++ ) {
		alias = NAMESPACE[ i ].alias;
		debug( 'Resolving `%s`', alias );

		fpath = resolve( NAMESPACE[ i ].path, ropts );
		debug( 'Resolved module path: %s', fpath );

		debug( 'Resolving package information.' );
		fpath = resolveParentPath( 'package.json', {
			'dir': dirname( fpath )
		});
		if ( fpath === null ) {
			debug( 'Unable to resolve package information.' );
			continue;
		}
		debug( 'Reading package information.' );
		pkg = readJSON( fpath, fopts );
		if ( pkg instanceof Error ) {
			debug( 'Unable to read package information: %s', pkg.message );
			continue;
		}
		debug( 'Checking package information.' );
		if ( pkg.name !== NAMESPACE[ i ].path ) {
			debug( 'Package information does not match path. Expected: %s. Actual: %s.', NAMESPACE[ i ].path, pkg.name );
			continue;
		}
		fpath = join( dirname( fpath ), REPL_TEXT );
		debug( 'Attempting to read REPL text: %s', fpath );

		file = readFile( fpath, fopts );
		if ( file instanceof Error ) {
			debug( 'Unable to read REPL text.' );
			continue;
		}
		debug( 'Successfully read REPL text.' );

		debug( 'Processing REPL text.' );
		file = replace( file, '{{alias}}', alias );

		debug( 'Resolving related aliases.' );
		len = NAMESPACE[ i ].related.length;
		if ( len ) {
			tmp = [];
			related = NAMESPACE[ i ].related;
			for ( j = 0; j < len; j++ ) {
				a = aliases[ related[ j ] ];
				if ( a === void 0 ) {
					debug( 'Unable to resolve related alias: %s.', related[ j ] );
				} else {
					tmp.push( a );
					debug( 'Resolved alias: %s => %s.', related[ j ], a );
				}
			}
			if ( tmp.length ) {
				file = replace( file, SEE_ALSO, SEE_ALSO+'    '+tmp.join( ', ' ) );
			} else {
				file = replace( file, SEE_ALSO, '' );
			}
		} else {
			file = replace( file, SEE_ALSO, '' );
		}
		debug( 'Successfully processed `%s`.', alias );
		out[ alias ] = file;
	}
	debug( 'Writing REPL text hash to file.' );
	out = PREAMBLE+'module.exports = '+JSON.stringify( out, null, '\t' )+';\n';
	writeFile( OUTPUT, out, fopts );
} // end FUNCTION createHelp()

/**
* Main execution sequence.
*
* @private
*/
function main() {
	debug( 'Generating REPL help documentation.' );
	createHelp();
} // end FUNCTION main()

// MAIN //

main();
<|endoftext|>