<commit_before>if (!RedactorPlugins) var RedactorPlugins = {};

RedactorPlugins.iframely = function() {
    return {

        init: function() {

            var that = this;

            var urlRe = /^\s*(https?:\/\/[^ \/,"]+\/[^ ,"]+)\s*$/i;

            this.$editor.on('keyup', function(e) {

                if (e.keyCode === 13) {
                    var current = that.selection.getCurrent();

                    while(current && current.noteType !== 3 && current.nodeName.toLowerCase() !== 'p') {
                        current = current.parentNode;
                    }

                    var prev = current.previousSibling;
                    if (!prev) {
                        return;
                    }
                    if (prev.getAttribute && prev.getAttribute('parsed-iframely-link')) {
                        return;
                    }

                    var text = prev.textContent;
                    if (!text) {
                        return;
                    }
                    var m = text.match(urlRe);
                    if (m) {
                        prev.setAttribute('parsed-iframely-link', '1');
                        that.iframely.fetchUrl(m[1], prev);
                    }
                }
            });
        },

        fetchUrl: function(uri, node) {

            var that = this;

            $.ajax({
                url: 'http://medium.iframe.ly/api/oembed',
                dataType: "json",
                data: {
                    url: uri,
                    iframe: true
                },
                success: function(data, textStatus, jqXHR) {

                    if (data && data.html) {
                        that.insert.html(data.html, false);
                    }

                    node && node.parentNode && node.parentNode.removeChild(node);
                },
                error: function(jqXHR, textStatus, errorThrown) {
                    console.error(jqXHR && jqXHR.responseText || textStatus);
                }
            });
        }
    };
};<commit_msg>handle oembed.type = 'photo'; use open iframely api<commit_after>if (!RedactorPlugins) var RedactorPlugins = {};

RedactorPlugins.iframely = function() {
    return {

        init: function() {

            var that = this;

            var urlRe = /^\s*(https?:\/\/[^ \/,"]+\/[^ ,"]+)\s*$/i;

            this.$editor.on('keyup', function(e) {

                if (e.keyCode === 13) {
                    var current = that.selection.getCurrent();

                    while(current && current.noteType !== 3 && current.nodeName.toLowerCase() !== 'p') {
                        current = current.parentNode;
                    }

                    var prev = current.previousSibling;
                    if (!prev) {
                        return;
                    }
                    if (prev.getAttribute && prev.getAttribute('parsed-iframely-link')) {
                        return;
                    }

                    var text = prev.textContent;
                    if (!text) {
                        return;
                    }
                    var m = text.match(urlRe);
                    if (m) {
                        prev.setAttribute('parsed-iframely-link', '1');
                        that.iframely.fetchUrl(m[1], prev);
                    }
                }
            });
        },

        fetchUrl: function(uri, node) {

            var that = this;

            $.ajax({
                url: 'http://open.iframe.ly/api/oembed',
                dataType: "json",
                data: {
                    url: uri,
                    origin: 'redactor'
                },
                success: function(data, textStatus, jqXHR) {

                    if (data && data.html) {
                        that.insert.html(data.html, false);
                    } else if (data && !data.html && data.type === 'photo' && data.url) {
                        that.insert.html('<img src="' + data.url + '" title="' + (data.title || data.url)  + '" alt="' + (data.title || data.url)  + '" />', false);
                    }

                    node && node.parentNode && node.parentNode.removeChild(node);
                },
                error: function(jqXHR, textStatus, errorThrown) {
                    console.error(jqXHR && jqXHR.responseText || textStatus);
                }
            });
        }
    };
};<|endoftext|>


<commit_before>import {
	call,
	put,
	select,
	takeLatest
} from 'redux-saga/effects'

import siSessionService from '../services/siSessionService'
import siSchedule from '../util/siSchedule'
import logger from '../util/logger'

const getSchedule = state => (state.schedule)
const getUserData = state => (state.user)

function* updateSISessions() {
	const { data, currentTerm } = yield select(getSchedule)
	const { isLoggedIn, profile } = yield select(getUserData)
	if (
		isLoggedIn &&
		profile.classifications.student &&
		(
			(currentTerm && currentTerm.term_code !== 'inactive' && data)
		)
	) {
		try {
			yield put({ type: 'GET_SI_SESSIONS_REQUEST' })
			const sessions = yield call(siSessionService.FetchSISessions)
			if (sessions) {
				const parsedSessions =  siSchedule.getSessions(sessions, data)
				yield put({ type: 'SET_SI_SESSIONS', parsedSessions })
				yield put({ type: 'GET_SI_SESSIONS_SUCCESS' })
			}
		} catch (error) {
			yield put({ type: 'GET_SI_SESSIONS_FAILURE', error })
			logger.trackException(error)
		}
	}
}

function* siSessionSaga() {
	yield takeLatest('UPDATE_SI_SESSIONS', updateSISessions)
}

export default siSessionSaga
<commit_msg>update siSessionSaga to handle new data<commit_after>import {
	call,
	put,
	select,
	takeLatest
} from 'redux-saga/effects'

import siSessionService from '../services/siSessionService'
import siSchedule from '../util/siSchedule'
import logger from '../util/logger'

const getSchedule = state => (state.schedule)
const getUserData = state => (state.user)

function* updateSISessions() {
	const { data, currentTerm } = yield select(getSchedule)
	const { isLoggedIn, profile } = yield select(getUserData)
	if (
		isLoggedIn &&
		profile.classifications.student &&
		(
			(currentTerm && currentTerm.term_code !== 'inactive' && data)
		)
	) {
		try {
			yield put({ type: 'GET_SI_SESSIONS_REQUEST' })
			const sessions = yield call(siSessionService.FetchSISessions)
			if (sessions) {
				yield put({ type: 'SET_SI_SESSIONS', sessions })
				yield put({ type: 'GET_SI_SESSIONS_SUCCESS' })
			}
		} catch (error) {
			yield put({ type: 'GET_SI_SESSIONS_FAILURE', error })
			logger.trackException(error)
		}
	}
}

function* siSessionSaga() {
	yield takeLatest('UPDATE_SI_SESSIONS', updateSISessions)
}

export default siSessionSaga
<|endoftext|>


<commit_before>/**
 * Returns a template function (function that accepts template parameters and returns a string)
 * to generate an aggregation query.
 * Valid options to define the query template are:
 * - placement
 * The query template parameters taken by the result template function are:
 * - sourceQuery
 * - res
 * - columns
 */
const templateForOptions = (options) => {
    let templateFn = aggregationQueryTemplates[options.placement];
    if (!templateFn) {
        throw new Error("Invalid Aggregation placement: '" + options.placement + "'");
    }
    return templateFn;
};

/**
 * Generates an aggregation query given the aggregation options:
 * - query
 * - resolution
 * - columns
 * - placement
 */
const queryForOptions = (options) => templateForOptions(options)({
    sourceQuery: options.query,
    res: options.resolution,
    columns: options.columns
});

module.exports = queryForOptions;

const SUPPORTED_AGGREGATE_FUNCTIONS = {
    'count': {
        sql: (column_name, params) => `count(${params.aggregated_column || '*'})`
    },
    'avg': {
        sql: (column_name, params) => `avg(${params.aggregated_column || column_name})`
    },
    'sum': {
        sql: (column_name, params) => `sum(${params.aggregated_column || column_name})`
    },
    'min': {
        sql: (column_name, params) => `min(${params.aggregated_column || column_name})`
    },
    'max': {
        sql: (column_name, params) => `max(${params.aggregated_column || column_name})`
    }
};

const aggregateColumns = ctx => {
    // TODO: always add count
    let columns = ctx.columns || {};
    if (Object.keys(columns).length === 0) {
        // default aggregation
        columns = {
            _cdb_feature_count: {
                aggregate_function: 'count'
            }
        };
    }
    return Object.keys(columns).map(column_name => {
        const aggregate_function = columns[column_name].aggregate_function || 'count';
        const aggregate_definition = SUPPORTED_AGGREGATE_FUNCTIONS[aggregate_function];
        if (!aggregate_definition) {
            throw new Error("Invalid Aggregate function: '" + aggregate_function + "'");
        }
        const aggregate_expression = aggregate_definition.sql(column_name, columns[column_name]);
        return `${aggregate_expression} AS ${column_name}`;
    }).join(', ');
};

// Notes:
// * ${256*0.00028/ctx.res}*!scale_denominator! is equivalent to
//   ${256/ctx.res}*CDB_XYZ_Resolution(CDB_ZoomFromScale(!scale_denominator!))
// * We need to filter spatially using !bbox! to make the queries efficient because
//   the filter added by Mapnik (wrapping the query)
//   is only applied after the aggregation.
// * This queries are used for rendering and the_geom is omitted in the results for better performance

const aggregationQueryTemplates = {
    'centroid': ctx => `
        WITH _cdb_params AS (
            SELECT
            (${256*0.00028/ctx.res}*!scale_denominator!)::double precision AS res,
            !bbox! AS bbox
        )
        SELECT
            row_number() over() AS cartodb_id,
            ST_SetSRID(
                ST_MakePoint(
                    AVG(ST_X(_cdb_query.the_geom_webmercator)),
                    AVG(ST_Y(_cdb_query.the_geom_webmercator))
                ), 3857
            ) AS the_geom_webmercator,
            ${aggregateColumns(ctx)}
        FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params
        WHERE _cdb_query.the_geom_webmercator && _cdb_params.bbox
        GROUP BY
            Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res),
            Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)
    `,

    'point-grid': ctx => `
    WITH _cdb_params AS (
        SELECT
        (${256*0.00028/ctx.res}*!scale_denominator!)::double precision AS res,
        !bbox! AS bbox
    ),
    _cdb_clusters AS (
        SELECT
        Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res)::int AS _cdb_gx,
        Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)::int AS _cdb_gy,
        ${aggregateColumns(ctx)}
        FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params
        WHERE the_geom_webmercator && _cdb_params.bbox
        GROUP BY _cdb_gx, _cdb_gy
    )
    SELECT
        ST_SetSRID(ST_MakePoint(_cdb_gx*(res+0.5), _cdb_gy*(res+0.5)), 3857) AS the_geom_webmercator,
        _cdb_feature_count
        FROM _cdb_clusters, _cdb_params
    `,

    'point-sample': ctx => `
        WITH _cdb_params AS (
            SELECT

            (${256*0.00028/ctx.res}*!scale_denominator!)::double precision AS res,
            !bbox! AS bbox
        ), _cdb_clusters AS (
            SELECT
                MIN(cartodb_id) AS cartodb_id,
                ${aggregateColumns(ctx)}
            FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params
            WHERE _cdb_query.the_geom_webmercator && _cdb_params.bbox
            GROUP BY
                Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res),
                Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)
        ) SELECT
            _cdb_clusters.cartodb_id,
            the_geom, the_geom_webmercator,
            _cdb_feature_count
        FROM
            _cdb_clusters INNER JOIN (${ctx.sourceQuery}) _cdb_query
            ON (_cdb_clusters.cartodb_id = _cdb_query.cartodb_id)
    `
};
<commit_msg>Always add a _cdb_feature_count to aggregated queries<commit_after>/**
 * Returns a template function (function that accepts template parameters and returns a string)
 * to generate an aggregation query.
 * Valid options to define the query template are:
 * - placement
 * The query template parameters taken by the result template function are:
 * - sourceQuery
 * - res
 * - columns
 */
const templateForOptions = (options) => {
    let templateFn = aggregationQueryTemplates[options.placement];
    if (!templateFn) {
        throw new Error("Invalid Aggregation placement: '" + options.placement + "'");
    }
    return templateFn;
};

/**
 * Generates an aggregation query given the aggregation options:
 * - query
 * - resolution
 * - columns
 * - placement
 */
const queryForOptions = (options) => templateForOptions(options)({
    sourceQuery: options.query,
    res: options.resolution,
    columns: options.columns
});

module.exports = queryForOptions;

const SUPPORTED_AGGREGATE_FUNCTIONS = {
    'count': {
        sql: (column_name, params) => `count(${params.aggregated_column || '*'})`
    },
    'avg': {
        sql: (column_name, params) => `avg(${params.aggregated_column || column_name})`
    },
    'sum': {
        sql: (column_name, params) => `sum(${params.aggregated_column || column_name})`
    },
    'min': {
        sql: (column_name, params) => `min(${params.aggregated_column || column_name})`
    },
    'max': {
        sql: (column_name, params) => `max(${params.aggregated_column || column_name})`
    }
};

const aggregateColumns = ctx => {
    let columns = Object.assign({
        _cdb_feature_count: {
            aggregate_function: 'count'
        }
    }, ctx.columns || {});
    return Object.keys(columns).map(column_name => {
        const aggregate_function = columns[column_name].aggregate_function || 'count';
        const aggregate_definition = SUPPORTED_AGGREGATE_FUNCTIONS[aggregate_function];
        if (!aggregate_definition) {
            throw new Error("Invalid Aggregate function: '" + aggregate_function + "'");
        }
        const aggregate_expression = aggregate_definition.sql(column_name, columns[column_name]);
        return `${aggregate_expression} AS ${column_name}`;
    }).join(', ');
};

// Notes:
// * ${256*0.00028/ctx.res}*!scale_denominator! is equivalent to
//   ${256/ctx.res}*CDB_XYZ_Resolution(CDB_ZoomFromScale(!scale_denominator!))
// * We need to filter spatially using !bbox! to make the queries efficient because
//   the filter added by Mapnik (wrapping the query)
//   is only applied after the aggregation.
// * This queries are used for rendering and the_geom is omitted in the results for better performance

const aggregationQueryTemplates = {
    'centroid': ctx => `
        WITH _cdb_params AS (
            SELECT
            (${256*0.00028/ctx.res}*!scale_denominator!)::double precision AS res,
            !bbox! AS bbox
        )
        SELECT
            row_number() over() AS cartodb_id,
            ST_SetSRID(
                ST_MakePoint(
                    AVG(ST_X(_cdb_query.the_geom_webmercator)),
                    AVG(ST_Y(_cdb_query.the_geom_webmercator))
                ), 3857
            ) AS the_geom_webmercator,
            ${aggregateColumns(ctx)}
        FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params
        WHERE _cdb_query.the_geom_webmercator && _cdb_params.bbox
        GROUP BY
            Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res),
            Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)
    `,

    'point-grid': ctx => `
    WITH _cdb_params AS (
        SELECT
        (${256*0.00028/ctx.res}*!scale_denominator!)::double precision AS res,
        !bbox! AS bbox
    ),
    _cdb_clusters AS (
        SELECT
        Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res)::int AS _cdb_gx,
        Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)::int AS _cdb_gy,
        ${aggregateColumns(ctx)}
        FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params
        WHERE the_geom_webmercator && _cdb_params.bbox
        GROUP BY _cdb_gx, _cdb_gy
    )
    SELECT
        ST_SetSRID(ST_MakePoint(_cdb_gx*(res+0.5), _cdb_gy*(res+0.5)), 3857) AS the_geom_webmercator,
        _cdb_feature_count
        FROM _cdb_clusters, _cdb_params
    `,

    'point-sample': ctx => `
        WITH _cdb_params AS (
            SELECT

            (${256*0.00028/ctx.res}*!scale_denominator!)::double precision AS res,
            !bbox! AS bbox
        ), _cdb_clusters AS (
            SELECT
                MIN(cartodb_id) AS cartodb_id,
                ${aggregateColumns(ctx)}
            FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params
            WHERE _cdb_query.the_geom_webmercator && _cdb_params.bbox
            GROUP BY
                Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res),
                Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)
        ) SELECT
            _cdb_clusters.cartodb_id,
            the_geom, the_geom_webmercator,
            _cdb_feature_count
        FROM
            _cdb_clusters INNER JOIN (${ctx.sourceQuery}) _cdb_query
            ON (_cdb_clusters.cartodb_id = _cdb_query.cartodb_id)
    `
};
<|endoftext|>


<commit_before>// Copyright (c) 2016 Drake Developers Club All Rights Reserved.

/* globals Promise */

/**
 * Defines GAPIManager object for promise oriented GAPI communication.
 *
 * Client library must be loaded after gapi-manager.js with callback named
 * 'GAPIManager', eg:
 * ```
 *   <script src="https://apis.google.com/js/client.js?onload=GAPIManager"></script>
 * ```
 */
(function() {
'use strict';

var _scopes = [];
var _clientId = '';
var _loadedAPIs = [];

var GAPIManager, _loadedGAPI;
(function() {
  var _onLoad;

  /**
   * Promise that resolves to the gapi object.
   */
  _loadedGAPI = new Promise(function(resolve) {
    _onLoad = function() {
      resolve(window.gapi);
    };
  });

  /**
   * Callback called when gapi loads.
   */
  GAPIManager = function() {
    _onLoad();
  };
})();

/**
 * Load an api with the given data.
 *
 * @return Promise that resolves to an API object.
 */
GAPIManager.loadAPI = function(name, version, apiRoot) {
  return _loadedGAPI
    .then(function($gapi) {
      var loadedAPI = new Promise(function(resolve, reject) {
        $gapi.client.load(name, version, null, apiRoot).then(function(resp) {
          if (resp && resp.error) {
            reject(new HTTPError(resp.error.code, resp.error.message));
          } else if (!$gapi.client[name]) {
            reject(new HTTPError(404, 'Not Found.'));
          } else {
            resolve(_patchifyAPI($gapi.client[name]));
          }
        });
      });
      _loadedAPIs.push(loadedAPI);
      return loadedAPI;
    });
};

/**
 * Finish loading all APIs set to load with APIManager.loadAPI.
 *
 * @return Promise that resolves when all loadAPI promises previously made
 *   have resolved.
 */
GAPIManager.loadAllAPIs = function() {
  return Promise.all(_loadedAPIs);
};

/**
 * Set scopes to authorize.
 *
 * This should be done before calling GAPIManager.authenticate().
 */
GAPIManager.setScopes = function(scopes) {
  _scopes = scopes;
};

/**
 * Set client ID to authorize with.
 *
 * This should be done before calling GAPIManager.authenticate().
 */
GAPIManager.setClientId = function(clientId) {
  _clientId = clientId;
};

/**
 * Authenticate with the clientId and scopes set previously.
 *
 * @return Promise that resolves with undefined when authenticated.
 */
GAPIManager.authorize = function(mode) {
  return _loadedGAPI
    .then(function($gapi) {
      return new Promise(function(resolve, reject) {
        $gapi.auth.authorize({
          client_id: _clientId, // jshint ignore:line
          scope: _scopes,
          immediate: mode,
          cookie_policy: window.location.origin // jshint ignore:line
        }, function(resp) {
          if (resp.error) {
            reject(new AuthError(resp.error, resp.error_subtype)); // jshint ignore:line
          } else {
            resolve(resp);
          }
        });
      });
    });
};

/**
 * Sign the currently authed user out.
 */
GAPIManager.signOut = function() {
  _loadedGAPI
    .then(function($gapi) {
      $gapi.auth.signOut();
    });
};

/**
 * Error with an HTTP status code, thrown when API request fails.
 */
var HTTPError = function(code, message) {
  Error.call(this);
  this.message = message;
  this.code = code;
};
HTTPError.prototype = Object.create(Error.prototype);
HTTPError.prototype.constructor = HTTPError;
GAPIManager.HTTPError = HTTPError;

/**
 * Error signaling authorization failed.
 */
var AuthError = function(errorType, errorSubtype) {
  Error.call(this);
  this.message = errorType + ': ' + errorSubtype;
  this.type = errorType;
  this.subtype = errorSubtype;

  this.accessDenied = (errorSubtype === 'access_denied');
};
AuthError.prototype = Object.create(Error.prototype);
AuthError.prototype.constructor = AuthError;
GAPIManager.AuthError = AuthError;

/**
 * Recursively crawl the API and return a modified copy that uses promises.
 */
var _patchifyAPI = function(apiObject, notInAPIRoot) {
  if (apiObject instanceof Function) {
    return function(params) {
      return new Promise(function(resolve, reject) {
        apiObject(params).execute(function(resp) {
          if (!resp) {
            reject(new HTTPError(404, 'Not Found.'));
          } else if (resp.code) {
            reject(new HTTPError(resp.code, resp.message));
          } else {
            resolve(resp);
          }
        });
      });
    };
  } else {
    var copy = {};
    Object.keys(apiObject).forEach(function(name) {
      if (notInAPIRoot || name !== 'kB') {
        copy[name] = _patchifyAPI(apiObject[name], true);
      }
    });
    return copy;
  }
};

window.GAPIManager = GAPIManager;

})();
<commit_msg>Improve gapi-manager.js documentation.<commit_after>// Copyright (c) 2016 Drake Developers Club All Rights Reserved.

/* globals Promise */

/**
 * Defines GAPIManager object for promise oriented GAPI communication.
 *
 * Client library must be loaded after gapi-manager.js with callback named
 * 'GAPIManager', eg:
 * ```
 *   <script src="https://apis.google.com/js/client.js?onload=GAPIManager"></script>
 * ```
 */
(function() {
'use strict';

var _scopes = [];
var _clientId = '';
var _loadedAPIs = [];

var GAPIManager, _loadedGAPI;
(function() {
  var _onLoad;

  /**
   * Promise that resolves to the gapi object.
   */
  _loadedGAPI = new Promise(function(resolve) {
    _onLoad = function() {
      resolve(window.gapi);
    };
  });

  /**
   * Callback called when gapi loads.
   */
  GAPIManager = function() {
    _onLoad();
  };
})();

/**
 * Load an api with the given data.
 *
 * @param {String} name - API name.
 * @param {String} version - API version.
 * @param {String} apiRoot - Root URI of the API, or undefined for Google APIs.
 * @return Promise that resolves to an API object.
 */
GAPIManager.loadAPI = function(name, version, apiRoot) {
  return _loadedGAPI
    .then(function($gapi) {
      var loadedAPI = new Promise(function(resolve, reject) {
        $gapi.client.load(name, version, null, apiRoot).then(function(resp) {
          if (resp && resp.error) {
            reject(new HTTPError(resp.error.code, resp.error.message));
          } else if (!$gapi.client[name]) {
            reject(new HTTPError(404, 'Not Found.'));
          } else {
            resolve(_patchifyAPI($gapi.client[name]));
          }
        });
      });
      _loadedAPIs.push(loadedAPI);
      return loadedAPI;
    });
};

/**
 * Finish loading all APIs set to load with APIManager.loadAPI.
 *
 * @return Promise that resolves when all loadAPI promises previously made
 *   have resolved.
 */
GAPIManager.loadAllAPIs = function() {
  return Promise.all(_loadedAPIs);
};

/**
 * Set scopes to authorize.
 *
 * This should be done before calling GAPIManager.authenticate().
 */
GAPIManager.setScopes = function(scopes) {
  _scopes = scopes;
};

/**
 * Set client ID to authorize with.
 *
 * This should be done before calling GAPIManager.authenticate().
 */
GAPIManager.setClientId = function(clientId) {
  _clientId = clientId;
};

/**
 * Authenticate with the clientId and scopes set previously.
 *
 * @return Promise that resolves with undefined when authenticated.
 */
GAPIManager.authorize = function(mode) {
  return _loadedGAPI
    .then(function($gapi) {
      return new Promise(function(resolve, reject) {
        $gapi.auth.authorize({
          client_id: _clientId, // jshint ignore:line
          scope: _scopes,
          immediate: mode,
          cookie_policy: window.location.origin // jshint ignore:line
        }, function(resp) {
          if (resp.error) {
            reject(new AuthError(resp.error, resp.error_subtype)); // jshint ignore:line
          } else {
            resolve(resp);
          }
        });
      });
    });
};

/**
 * Sign the currently authed user out.
 */
GAPIManager.signOut = function() {
  _loadedGAPI
    .then(function($gapi) {
      $gapi.auth.signOut();
    });
};

/**
 * Error with an HTTP status code, thrown when API request fails.
 */
var HTTPError = function(code, message) {
  Error.call(this);
  this.message = message;
  this.code = code;
};
HTTPError.prototype = Object.create(Error.prototype);
HTTPError.prototype.constructor = HTTPError;
GAPIManager.HTTPError = HTTPError;

/**
 * Error signaling authorization failed.
 */
var AuthError = function(errorType, errorSubtype) {
  Error.call(this);
  this.message = errorType + ': ' + errorSubtype;
  this.type = errorType;
  this.subtype = errorSubtype;

  this.accessDenied = (errorSubtype === 'access_denied');
};
AuthError.prototype = Object.create(Error.prototype);
AuthError.prototype.constructor = AuthError;
GAPIManager.AuthError = AuthError;

/**
 * Recursively crawl the API and return a modified copy that uses promises.
 */
var _patchifyAPI = function(apiObject, notInAPIRoot) {
  if (apiObject instanceof Function) {
    return function(params) {
      return new Promise(function(resolve, reject) {
        apiObject(params).execute(function(resp) {
          if (!resp) {
            reject(new HTTPError(404, 'Not Found.'));
          } else if (resp.code) {
            reject(new HTTPError(resp.code, resp.message));
          } else {
            resolve(resp);
          }
        });
      });
    };
  } else {
    var copy = {};
    Object.keys(apiObject).forEach(function(name) {
      if (notInAPIRoot || name !== 'kB') {
        copy[name] = _patchifyAPI(apiObject[name], true);
      }
    });
    return copy;
  }
};

window.GAPIManager = GAPIManager;

})();
<|endoftext|>


<commit_before>const fs = require('fs');
const stream = require('stream');
const readline = require('readline');
const http = require('http');
const os = require('os');
const path = require('path');
const logger = require('../helpers/logger');

const {
  DEFAULT_GFWLIST_PATH
} = require('../constants');

const {
  MAIN_START_PAC,
  MAIN_STOP_PAC,
  RENDERER_START_PAC,
  RENDERER_STOP_PAC
} = require('../../defs/events');

// cache adp-scripts.js into memory
const adpScripts = fs.readFileSync(path.join(__dirname, '..', 'resources', 'adp-scripts.js'));

/**
 * A http service which serves pac file
 */
class PacService {

  constructor() {
    this._server = null;
  }

  isRunning() {
    return this._server !== null;
  }

  start({host, port, proxyHost, proxyPort, rules}) {
    if (!this._server) {
      const fileData = this._assemble({host: proxyHost, port: proxyPort, rules});
      this._server = http.createServer((req, res) => {
        res.writeHead(200, {
          'Server': 'blinksocks-desktop',
          'Content-Type': 'application/x-ns-proxy-autoconfig',
          'Content-Length': fileData.length,
          'Cache-Control': 'no-cache',
          'Date': (new Date).toUTCString(),
          'Connection': 'Close'
        });
        res.end(fileData);
      });
      this._server.on('clientError', (err, socket) => {
        socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
      });
      this._server.listen(port, () => {
        logger.info(`started local pac server at http://${host}:${port}`);
      });
    }
  }

  stop() {
    if (this._server) {
      this._server.close();
      this._server = null;
      logger.info('stopped local pac server');
    }
  }

  _assemble({host, port, rules}) {
    const _rules = JSON.stringify(rules, null, '  ');
    return `
var proxy = "SOCKS5 ${host}:${port}; SOCKS ${host}:${port}; DIRECT;";
var direct = 'DIRECT;';
var rules = ${_rules};

${adpScripts}

var defaultMatcher = new CombinedMatcher();

for (var i = 0; i < rules.length; i++) {
  defaultMatcher.add(Filter.fromText(rules[i]));
}

function FindProxyForURL(url, host) {
  if (defaultMatcher.matchesAny(url, host) instanceof BlockingFilter) {
    return proxy;
  }
  return direct;
}
`;
  }

}

/**
 * parse gfwlist line by line
 * @param filePath
 * @returns {Promise}
 */
function parseRules(filePath) {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, 'utf8', (err, data) => {
      if (err) {
        reject(err);
      } else {
        const sr = new stream.PassThrough();
        sr.end(Buffer.from(data, 'base64').toString('ascii'));
        const rl = readline.createInterface({input: sr});
        let index = 0;
        const domains = [];
        rl.on('line', (line) => {
          if (!(line.startsWith('!')) && line.length > 0 && index !== 0) {
            domains.push(line);
          }
          index += 1;
        });
        rl.on('close', () => resolve(domains));
      }
    });
  });
}

module.exports = function pacModule({onStatusChange}) {

  let pacService = new PacService();

  /**
   * start local pac service
   * @param push
   * @param type
   * @param host
   * @param port
   * @param proxyHost
   * @param proxyPort
   * @param customRules
   * @returns {Promise.<void>}
   */
  async function start(push, {type, host, port, proxyHost, proxyPort, customRules}) {
    // start pac service only in local mode
    if (type === 0) {
      // parse custom rules
      if (Array.isArray(customRules)) {
        customRules = customRules.filter((rule) => rule.length > 0);
      } else {
        customRules = [];
      }
      // parse gfwlist.txt
      const builtInRules = await parseRules(DEFAULT_GFWLIST_PATH);
      pacService.start({host, port, proxyHost, proxyPort, rules: customRules.concat(builtInRules)});
    }
    push(MAIN_START_PAC);
    onStatusChange(true);
  }

  /**
   * stop local pac service
   */
  function stop(push) {
    pacService.stop();
    push(MAIN_STOP_PAC);
    onStatusChange(false);
  }

  return {
    [RENDERER_START_PAC]: start,
    [RENDERER_STOP_PAC]: stop,
  };
};
<commit_msg>chore(pac): catch errors when fail to start pac service<commit_after>const fs = require('fs');
const stream = require('stream');
const readline = require('readline');
const http = require('http');
const os = require('os');
const path = require('path');
const logger = require('../helpers/logger');

const {
  DEFAULT_GFWLIST_PATH
} = require('../constants');

const {
  MAIN_START_PAC,
  MAIN_STOP_PAC,
  RENDERER_START_PAC,
  RENDERER_STOP_PAC
} = require('../../defs/events');

// cache adp-scripts.js into memory
const adpScripts = fs.readFileSync(path.join(__dirname, '..', 'resources', 'adp-scripts.js'));

/**
 * A http service which serves pac file
 */
class PacService {

  constructor() {
    this._server = null;
  }

  isRunning() {
    return this._server !== null;
  }

  start({host, port, proxyHost, proxyPort, rules}) {
    if (!this._server) {
      const fileData = this._assemble({host: proxyHost, port: proxyPort, rules});
      this._server = http.createServer((req, res) => {
        res.writeHead(200, {
          'Server': 'blinksocks-desktop',
          'Content-Type': 'application/x-ns-proxy-autoconfig',
          'Content-Length': fileData.length,
          'Cache-Control': 'no-cache',
          'Date': (new Date).toUTCString(),
          'Connection': 'Close'
        });
        res.end(fileData);
      });
      this._server.on('clientError', (err, socket) => {
        socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
      });
      this._server.listen(port, () => {
        logger.info(`started local pac server at http://${host}:${port}`);
      });
    }
  }

  stop() {
    if (this._server) {
      this._server.close();
      this._server = null;
      logger.info('stopped local pac server');
    }
  }

  _assemble({host, port, rules}) {
    const _rules = JSON.stringify(rules, null, '  ');
    return `
var proxy = "SOCKS5 ${host}:${port}; SOCKS ${host}:${port}; DIRECT;";
var direct = 'DIRECT;';
var rules = ${_rules};

${adpScripts}

var defaultMatcher = new CombinedMatcher();

for (var i = 0; i < rules.length; i++) {
  defaultMatcher.add(Filter.fromText(rules[i]));
}

function FindProxyForURL(url, host) {
  if (defaultMatcher.matchesAny(url, host) instanceof BlockingFilter) {
    return proxy;
  }
  return direct;
}
`;
  }

}

/**
 * parse gfwlist line by line
 * @param filePath
 * @returns {Promise}
 */
function parseRules(filePath) {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, 'utf8', (err, data) => {
      if (err) {
        reject(err);
      } else {
        const sr = new stream.PassThrough();
        sr.end(Buffer.from(data, 'base64').toString('ascii'));
        const rl = readline.createInterface({input: sr});
        let index = 0;
        const domains = [];
        rl.on('line', (line) => {
          if (!(line.startsWith('!')) && line.length > 0 && index !== 0) {
            domains.push(line);
          }
          index += 1;
        });
        rl.on('close', () => resolve(domains));
      }
    });
  });
}

module.exports = function pacModule({onStatusChange}) {

  let pacService = new PacService();

  /**
   * start local pac service
   * @param push
   * @param type
   * @param host
   * @param port
   * @param proxyHost
   * @param proxyPort
   * @param customRules
   * @returns {Promise.<void>}
   */
  async function start(push, {type, host, port, proxyHost, proxyPort, customRules}) {
    try {
      // start pac service only in local mode
      if (type === 0) {
        // parse custom rules
        if (Array.isArray(customRules)) {
          customRules = customRules.filter((rule) => rule.length > 0);
        } else {
          customRules = [];
        }
        // parse gfwlist.txt
        const builtInRules = await parseRules(DEFAULT_GFWLIST_PATH);
        pacService.start({host, port, proxyHost, proxyPort, rules: customRules.concat(builtInRules)});
      }
      push(MAIN_START_PAC);
      onStatusChange(true);
    } catch (err) {
      logger.error(err);
    }
  }

  /**
   * stop local pac service
   */
  function stop(push) {
    pacService.stop();
    push(MAIN_STOP_PAC);
    onStatusChange(false);
  }

  return {
    [RENDERER_START_PAC]: start,
    [RENDERER_STOP_PAC]: stop,
  };
};
<|endoftext|>


<commit_before>/*global MfiaClient, Backbone, JST*/

MfiaClient.Views = MfiaClient.Views || {};

(function () {
  'use strict';

  MfiaClient.Views.Header = Backbone.View.extend({

    template: JST['app/scripts/templates/header.ejs'],

    tagName: 'div',

    id: '',

    events: {
        'click #nav-about': 'goToAbout',
        'click #nav-projects': 'goToProjects',
        'click #logo-container': 'goToHome',
        'change .chosen-select': 'doSearch'
    },
    aboutSectionVisible: true,
    aboutSectionOpenOnce: false,
    goToAbout: function(evt) {
        Backbone.history.navigate('about', {'trigger': true });
    },
    goToProjects: function(evt) {
        Backbone.history.navigate('projects/', {'trigger': true })
    },
    goToHome: function(evt) {
        Backbone.history.navigate('', {'trigger': true })
    },
    initialize: function () {
        var that = this;

        MfiaClient.app.on("loading", function() {
            console.log("loading");
            that.$(".progress").show();
        });
        MfiaClient.app.on("loaded", function() {
            console.log("loaded");
            that.$(".progress").hide();
        });
    },
    doSearch: function() {
        var search = "#projects?" + this.$(".chosen-select").val();
        if (!this.aboutSectionOpenOnce) {
            this.aboutSectionOpenOnce = true;
        }

        Backbone.history.navigate(search, {'trigger': true});
    },
    render: function () {
        var options = {};
        options.data = this.model;
        this.$el.html(this.template(options));
        return this;
    },
    onShow: function() {
        var that = this;
        this.$(".button-collapse").sideNav();
        this.$(".chosen-select").chosen({width: "90%"});
    }
  });
})();
<commit_msg>Alter default options for Chosen, such as placeholder and search prompt<commit_after>/*global MfiaClient, Backbone, JST*/

MfiaClient.Views = MfiaClient.Views || {};

(function () {
  'use strict';

  MfiaClient.Views.Header = Backbone.View.extend({

    template: JST['app/scripts/templates/header.ejs'],

    tagName: 'div',

    id: '',

    events: {
        'click #nav-about': 'goToAbout',
        'click #nav-projects': 'goToProjects',
        'click #logo-container': 'goToHome',
        'change .chosen-select': 'doSearch'
    },
    aboutSectionVisible: true,
    aboutSectionOpenOnce: false,
    goToAbout: function(evt) {
        Backbone.history.navigate('about', {'trigger': true });
    },
    goToProjects: function(evt) {
        Backbone.history.navigate('projects/', {'trigger': true })
    },
    goToHome: function(evt) {
        Backbone.history.navigate('', {'trigger': true })
    },
    initialize: function () {
        var that = this;

        MfiaClient.app.on("loading", function() {
            console.log("loading");
            that.$(".progress").show();
        });
        MfiaClient.app.on("loaded", function() {
            console.log("loaded");
            that.$(".progress").hide();
        });
    },
    doSearch: function() {
        var search = "#projects?" + this.$(".chosen-select").val();
        if (!this.aboutSectionOpenOnce) {
            this.aboutSectionOpenOnce = true;
        }

        Backbone.history.navigate(search, {'trigger': true});
    },
    render: function () {
        var options = {};
        options.data = this.model;
        this.$el.html(this.template(options));
        return this;
    },
    onShow: function() {
        var that = this;
        this.$(".button-collapse").sideNav();
        this.$(".chosen-select").chosen({
            width: "100%",
            no_results_text: "No results found for"
        });
        this.$(".chosen-container-single .chosen-search input").attr("placeholder", "Scroll or start typing...");
        this.$("a.chosen-single span").text("Viewing all projects");
    }
  });
})();
<|endoftext|>


<commit_before>import {extend} from './utils'
import UIObject from './ui_object'

export default class UICorePlugin extends UIObject {
  constructor(core) {
    super(core)
    this.core = core
    this.enabled = true
    this.bindEvents()
    this.render()
  }

  bindEvents() {}

  getExternalInterface() { return {} }

  enable() {
    if (!this.enabled) {
      this.bindEvents()
      this.$el.show()
      this.enabled = true
    }
  }

  disable() {
    this.stopListening()
    this.$el.hide()
    this.enabled = false
  }

  destroy() {
    this.remove()
  }

  render() {
    this.$el.html(this.template())
    this.$el.append(this.styler.getStyleFor(this.name))
    this.core.$el.append(this.el)
    return this
  }
}

UICorePlugin.extend = function(properties) {
  return extend(UICorePlugin, properties)
}

UICorePlugin.type = 'core'<commit_msg>todo: check if code is needed<commit_after>import {extend} from './utils'
import UIObject from './ui_object'

export default class UICorePlugin extends UIObject {
  constructor(core) {
    super(core)
    this.core = core
    this.enabled = true
    this.bindEvents()
    this.render()
  }

  bindEvents() {}

  getExternalInterface() { return {} }

  enable() {
    if (!this.enabled) {
      this.bindEvents()
      this.$el.show()
      this.enabled = true
    }
  }

  disable() {
    this.stopListening()
    this.$el.hide()
    this.enabled = false
  }

  destroy() {
    this.remove()
  }

  //TODO: this seems never be called and assume template and styler, remove it or add template and styler
  render() {
    this.$el.html(this.template())
    this.$el.append(this.styler.getStyleFor(this.name))
    this.core.$el.append(this.el)
    return this
  }
}

UICorePlugin.extend = function(properties) {
  return extend(UICorePlugin, properties)
}

UICorePlugin.type = 'core'<|endoftext|>


<commit_before>import {default as React, Component} from 'react';
import classNames from 'classnames';
import { manager } from '../middleware/ChannelManager.js';
var helper = require('../middleware/helper.js');

export class ToggleButton extends Component {
	constructor(props, context) {
		super(props);
		this.state = {
			selected: []
		};
		this.type = 'term';
		this.defaultSelected = this.props.defaultSelected;
		this.handleChange = this.handleChange.bind(this);
		this.defaultQuery = this.defaultQuery.bind(this);
	}

	// Set query information
	componentDidMount() {
		this.setQueryInfo();
		if(this.defaultSelected) {
			let records = this.props.data.filter((record) => {
				return this.defaultSelected.indexOf(record.label) > -1 ? true : false;
			});
			if(records && records.length) {
				records.forEach((singleRecord) => {
					this.handleChange(singleRecord);
				});
			}
		}
	}

	componentWillUpdate() {
		if(this.defaultSelected != this.props.defaultSelected) {
			this.defaultSelected = this.props.defaultSelected;
			let records = this.props.data.filter((record) => {
				return this.defaultSelected.indexOf(record.label) > -1 ? true : false;
			});
			if(records && records.length) {
				records.forEach((singleRecord) => {
					this.handleChange(singleRecord);
				});
			}
		}
	}

	// set the query type and input data
	setQueryInfo() {
		let obj = {
			key: this.props.sensorId,
			value: {
				queryType: this.type,
				inputData: this.props.appbaseField,
				defaultQuery: this.defaultQuery
			}
		};
		helper.selectedSensor.setSensorInfo(obj);
	}

	// build query for this sensor only
	defaultQuery(record) {
		let query = null;
		if(record && record.length) {
			query = {
				bool: {
					should: generateRangeQuery(this.props.appbaseField),
					"minimum_should_match" : 1,
					"boost" : 1.0
				}
			};
			console.log(query);
			return query;
		} else {
			return query;
		}
		function generateRangeQuery(appbaseField) {
			return record.map((singleRecord, index) => {
				return {
					term: {
						[appbaseField]: singleRecord.value
					}
				};
			});
		}
	}

	// use this only if want to create actuators
	// Create a channel which passes the depends and receive results whenever depends changes
	createChannel() {
		let depends = this.props.depends ? this.props.depends : {};
		var channelObj = manager.create(this.context.appbaseRef, this.context.type, depends);
	}

	// handle the input change and pass the value inside sensor info
	handleChange(record) {
		let selected = this.state.selected;
		let newSelection = [];
		let selectedIndex = null;
		let isAlreadySelected = selected.forEach((selectedRecord, index) => {
			if(record.label === selectedRecord.label) {
				selectedIndex = index;
				selected.splice(index, 1);
			}
		});
		if(selectedIndex === null) {
			if(this.props.multiSelect) {
				selected.push(record);
				newSelection = selected;
			} else {
				newSelection.push(record);
			}
		}
		this.setState({
			'selected': newSelection
		});
		var obj = {
			key: this.props.sensorId,
			value: newSelection
		};
		// pass the selected sensor value with sensorId as key,
		let isExecuteQuery = true;
		helper.selectedSensor.set(obj, isExecuteQuery);
	}

	renderButtons() {
		let buttons;
		let selectedText = this.state.selected.map((record) => {
			return record.label;
		});
		if(this.props.data) {
			buttons = this.props.data.map((record, i) => {
				return (
					<button key={i} className={"btn rbc-btn "+ (selectedText.indexOf(record.label) > -1 ? 'rbc-btn-active' : 'rbc-btn-inactive')}
						onClick={() => this.handleChange(record)} title={record.title ? record.title: record.label}>
						{record.label}
					</button>
				);
			});
		}
		return buttons;
	}

	// render
	render() {
		let title = null;
		if(this.props.title) {
			title = (
{this.props.title}
);
		}

		let cx = classNames({
			'rbc-title-active': this.props.title,
			'rbc-title-inactive': !this.props.title,
			'rbc-multiselect-active': this.props.multiSelect,
			'rbc-multiselect-inactive': !this.props.multiSelect
		});
		return (
			<div className={`rbc rbc-togglebutton col s12 col-xs-12 card thumbnail ${cx}`} style={this.props.defaultStyle}>

					{title}

						{this.renderButtons()}

		);
	}
}

ToggleButton.propTypes = {
	sensorId: React.PropTypes.string.isRequired,
	appbaseField: React.PropTypes.string.isRequired,
	title: React.PropTypes.string,
	data: React.PropTypes.any.isRequired,
	defaultSelected: React.PropTypes.array,
	multiSelect: React.PropTypes.bool
};

// Default props value
ToggleButton.defaultProps = {
	multiSelect: true
};

// context type
ToggleButton.contextTypes = {
	appbaseRef: React.PropTypes.any.isRequired,
	type: React.PropTypes.any.isRequired
};<commit_msg>fix toggleButton untoggle issue<commit_after>import {default as React, Component} from 'react';
import classNames from 'classnames';
import { manager } from '../middleware/ChannelManager.js';
var helper = require('../middleware/helper.js');

export class ToggleButton extends Component {
	constructor(props, context) {
		super(props);
		this.state = {
			selected: []
		};
		this.type = 'term';
		this.defaultSelected = this.props.defaultSelected;
		this.handleChange = this.handleChange.bind(this);
		this.defaultQuery = this.defaultQuery.bind(this);
	}

	// Set query information
	componentDidMount() {
		this.setQueryInfo();
		if(this.defaultSelected) {
			let records = this.props.data.filter((record) => {
				return this.defaultSelected.indexOf(record.label) > -1 ? true : false;
			});
			if(records && records.length) {
				records.forEach((singleRecord) => {
					this.handleChange(singleRecord);
				});
			}
		}
	}

	componentWillUpdate() {
		if(this.defaultSelected != this.props.defaultSelected) {
			this.defaultSelected = this.props.defaultSelected;
			let records = this.props.data.filter((record) => {
				return this.defaultSelected.indexOf(record.label) > -1 ? true : false;
			});
			if(records && records.length) {
				records.forEach((singleRecord) => {
					this.handleChange(singleRecord);
				});
			}
		}
	}

	// set the query type and input data
	setQueryInfo() {
		let obj = {
			key: this.props.sensorId,
			value: {
				queryType: this.type,
				inputData: this.props.appbaseField,
				defaultQuery: this.defaultQuery
			}
		};
		helper.selectedSensor.setSensorInfo(obj);
	}

	// build query for this sensor only
	defaultQuery(record) {
		let query = null;
		if(record && record.length) {
			query = {
				bool: {
					should: generateRangeQuery(this.props.appbaseField),
					"minimum_should_match" : 1,
					"boost" : 1.0
				}
			};
			console.log(query);
			return query;
		} else {
			return query;
		}
		function generateRangeQuery(appbaseField) {
			return record.map((singleRecord, index) => {
				return {
					term: {
						[appbaseField]: singleRecord.value
					}
				};
			});
		}
	}

	// use this only if want to create actuators
	// Create a channel which passes the depends and receive results whenever depends changes
	createChannel() {
		let depends = this.props.depends ? this.props.depends : {};
		var channelObj = manager.create(this.context.appbaseRef, this.context.type, depends);
	}

	// handle the input change and pass the value inside sensor info
	handleChange(record) {
		let selected = this.state.selected;
		let newSelection = [];
		let selectedIndex = null;
		selected.forEach((selectedRecord, index) => {
			if(record.label === selectedRecord.label) {
				selectedIndex = index;
				selected.splice(index, 1);
			}
		});
		if(selectedIndex === null) {
			if(this.props.multiSelect) {
				selected.push(record);
				newSelection = selected;
			} else {
				newSelection.push(record);
			}
		} else {
			newSelection = selected;
		}
		this.setState({
			'selected': newSelection
		});
		var obj = {
			key: this.props.sensorId,
			value: newSelection
		};
		// pass the selected sensor value with sensorId as key,
		let isExecuteQuery = true;
		helper.selectedSensor.set(obj, isExecuteQuery);
	}

	renderButtons() {
		let buttons;
		let selectedText = this.state.selected.map((record) => {
			return record.label;
		});
		if(this.props.data) {
			buttons = this.props.data.map((record, i) => {
				return (
					<button key={i} className={"btn rbc-btn "+ (selectedText.indexOf(record.label) > -1 ? 'rbc-btn-active' : 'rbc-btn-inactive')}
						onClick={() => this.handleChange(record)} title={record.title ? record.title: record.label}>
						{record.label}
					</button>
				);
			});
		}
		return buttons;
	}

	// render
	render() {
		let title = null;
		if(this.props.title) {
			title = (
{this.props.title}
);
		}

		let cx = classNames({
			'rbc-title-active': this.props.title,
			'rbc-title-inactive': !this.props.title,
			'rbc-multiselect-active': this.props.multiSelect,
			'rbc-multiselect-inactive': !this.props.multiSelect
		});
		return (
			<div className={`rbc rbc-togglebutton col s12 col-xs-12 card thumbnail ${cx}`} style={this.props.defaultStyle}>

					{title}

						{this.renderButtons()}

		);
	}
}

ToggleButton.propTypes = {
	sensorId: React.PropTypes.string.isRequired,
	appbaseField: React.PropTypes.string.isRequired,
	title: React.PropTypes.string,
	data: React.PropTypes.any.isRequired,
	defaultSelected: React.PropTypes.array,
	multiSelect: React.PropTypes.bool
};

// Default props value
ToggleButton.defaultProps = {
	multiSelect: true
};

// context type
ToggleButton.contextTypes = {
	appbaseRef: React.PropTypes.any.isRequired,
	type: React.PropTypes.any.isRequired
};
<|endoftext|>


<commit_before>import moment from "moment";

Meteor.methods({
  currentUserCanAccessHome(homeId) {
    const currentUserId = Meteor.userId();

    const home = Homes.findOne(homeId);

    const userGroupIds = Meteor.call("getSingleUserGroupIds", currentUserId);

    const userIsInHomeGroup = userGroupIds.includes(home.groupId);

    const userIsAdmin = Roles.userIsInRole(currentUserId, ["admin"]);

    if (userIsInHomeGroup || userIsAdmin) {
      return true;
    }

    return false;
  },
  getHomeResidentIds: function(homeId) {
    // Get all residents of specific home
    var residents = Residents.find({ homeId: homeId }).fetch();

    // Create an array containing only resident IDs
    var residentIds = _.map(residents, function(resident) {
      return resident._id;
    });

    // return the resident IDs array
    return residentIds;
  },
  getHomeCurrentResidencies(homeId) {
    /*
    Get all residencies for a home where the resident has not moved out.
     */
    // Get all residents of specific home who are not departed
    return Residencies.find({
      homeId,
      moveOut: {
        $exists: false
      }
    }).fetch();
  },
  getHomeCurrentResidentIds(homeId) {
    /*
    Get all resident IDs for a given home based on residency status.
     */
    const homeCurrentResidencies = Meteor.call(
      "getHomeCurrentResidencies",
      homeId
    );

    const homeCurrentResidentIds = _.map(homeCurrentResidencies, function(
      residency
    ) {
      return residency.residentId;
    });

    return homeCurrentResidentIds;
  },
  getHomeCurrentAndActiveResidentIds: function(homeId) {
    /*
    Get all residents of specific home who have an active residency and are not on hiatus
     */
    const onHiatus = false;

    const currentResidentIds = Meteor.call("getHomeCurrentResidentIds", homeId);

    const residents = Residents.find(
      {
        _id: {
          $in: currentResidentIds
        },
        onHiatus
      },
      { sort: { firstName: 1 } }
    ).fetch();

    // Create an array containing only resident IDs
    const residentIds = _.map(residents, function(resident) {
      return resident._id;
    });

    // return the resident IDs array
    return residentIds;
  },
  getHomeCurrentAndActiveResidentCount: function(homeId) {
    // Get all current residents for specific home (not departed or on hiatus)
    const homeCurrentResidentIds = Meteor.call(
      "getHomeCurrentAndActiveResidentIds",
      homeId
    );

    // Count the length of current resident IDs list
    const homeCurrentResidentsCount = homeCurrentResidentIds.length;

    return homeCurrentResidentsCount;
  },
  getHomeActivities: function(homeId) {
    // Get all resident of this home
    var homeResidentIds = Meteor.call("getHomeCurrentResidentIds", homeId);

    // Get an array of all activity Ids for residents of this home
    var homeResidentActivitiesQuery = Activities.find(
      { residentIds: { $elemMatch: { $in: homeResidentIds } } },
      { sort: { activityDate: -1 } }
    );

    // return all activities from activity IDs array
    var homeActivities = homeResidentActivitiesQuery.fetch();

    // Create a custom activities array using collection helpers
    // for resident names, activity type, and time ago
    var homeActivitiesArray = homeActivities.map(function(activity) {
      return {
        residents: activity.residentNames(),
        type: activity.activityType(),
        duration: activity.duration,
        activityDate: activity.activityDate
      };
    });

    return homeActivitiesArray;
  },
  getHomeActivityLevelCounts: function(homeId) {
    // // Get home residents by calling getHomeResidentIds
    var residentIds = Meteor.call("getHomeCurrentAndActiveResidentIds", homeId);

    var residentActivityLevelCounts = {
      inactive: 0,
      semiActive: 0,
      active: 0
    };

    if (residentIds) {
      // Get count of recent active days for each resident
      _.each(residentIds, function(residentId) {
        const residentRecentActiveDaysCount = Meteor.call(
          "getResidentRecentActiveDaysCount",
          residentId
        );

        // Check resident activity level
        if (residentRecentActiveDaysCount === 0) {
          // If zero activities, resident is inactive
          residentActivityLevelCounts.inactive += 1;
        } else if (residentRecentActiveDaysCount < 5) {
          // If less than five activities, resident is semi-active
          // TODO: refactor to use activity threshold variable
          residentActivityLevelCounts.semiActive += 1;
        } else if (residentRecentActiveDaysCount >= 5) {
          // If greater than or equal to five activities, resident is active
          // TODO: refactor to use activity threshold variable
          residentActivityLevelCounts.active += 1;
        }
      });
    }

    return residentActivityLevelCounts;
  },
  getHomeActivityCountTrend(homeId) {
    // Get home residents
    const residentIds = Meteor.call(
      "getHomeCurrentAndActiveResidentIds",
      homeId
    );

    // Arrays for daily activity level counts
    const inactivityCounts = [];
    const semiActivityCounts = [];
    const activityCounts = [];
    const date = [];

    // Date one week ago (six days, since today counts as one day)
    const startDate = moment().subtract(6, "days");

    // Get a date object for the end of day today
    const endDate = moment().endOf("day");

    for (
      let currentDay = moment(startDate);
      currentDay.isBefore(endDate);
      currentDay.add(1, "day")
    ) {
      // Set up placeholder activity counts object
      const dailyActivityCounts = {
        date: currentDay.toDate(),
        inactive: 0,
        semiActive: 0,
        active: 0
      };

      // Get activity level for each resident
      // Compare it against the baseline
      _.each(residentIds, function(residentId) {
        const result = Meteor.call(
          "getResidentRecentActiveDaysCount",
          residentId,
          currentDay.toDate()
        );

        if (result === 0) {
          dailyActivityCounts.inactive += 1;
        } else if (result < 5 && result > 0) {
          dailyActivityCounts.semiActive += 1;
        } else if (result >= 5) {
          dailyActivityCounts.active += 1;
        }
      });

      // Add daily activity levels to activity counts arrays
      inactivityCounts.push(dailyActivityCounts.inactive);
      semiActivityCounts.push(dailyActivityCounts.semiActive);
      activityCounts.push(dailyActivityCounts.active);
      date.push(dailyActivityCounts.date);
    }

    return { activityCounts, inactivityCounts, semiActivityCounts, date };
  },
  getHomeSelectOptionsWithGroups() {
    // Get all Groups
    var groups = Groups.find().fetch();

    // Create an array of Group IDs
    var groupIDs = _.map(groups, function(group) {
      return group._id;
    });

    // Create select options for Homes input
    // Grouping homes by group
    var homeSelectOptionsWithGroups = _.map(groupIDs, function(groupId) {
      // Find the name of this group
      var groupName = Groups.findOne(groupId).name;

      // Get all homes of this group
      var groupHomes = Homes.find({ groupId: groupId }).fetch();

      // Create a homes array with name/ID pairs for label/value
      var homesOptions = _.map(groupHomes, function(groupHome) {
        // Combine resident first name and last initial
        var homeName = groupHome.name;

        // Create option for this home, with home ID as the value
        return { label: homeName, value: groupHome._id };
      });

      // Return residents and home as option group
      return { optgroup: groupName, options: homesOptions };
    });

    return homeSelectOptionsWithGroups;
  },
  getUserVisibleHomes(userId) {
    const userPermissions = Permissions.find({ userId }).fetch();

    const userGroups = userPermissions.map(permission => permission.groupId);

    return Homes.find({ groupId: { $in: userGroups } }).fetch();
  }
});
<commit_msg>Fix "get user visible home IDs" method<commit_after>import moment from "moment";

Meteor.methods({
  currentUserCanAccessHome(homeId) {
    const currentUserId = Meteor.userId();

    const home = Homes.findOne(homeId);

    const userGroupIds = Meteor.call("getSingleUserGroupIds", currentUserId);

    const userIsInHomeGroup = userGroupIds.includes(home.groupId);

    const userIsAdmin = Roles.userIsInRole(currentUserId, ["admin"]);

    if (userIsInHomeGroup || userIsAdmin) {
      return true;
    }

    return false;
  },
  getHomeResidentIds: function(homeId) {
    // Get all residents of specific home
    var residents = Residents.find({ homeId: homeId }).fetch();

    // Create an array containing only resident IDs
    var residentIds = _.map(residents, function(resident) {
      return resident._id;
    });

    // return the resident IDs array
    return residentIds;
  },
  getHomeCurrentResidencies(homeId) {
    /*
    Get all residencies for a home where the resident has not moved out.
     */
    // Get all residents of specific home who are not departed
    return Residencies.find({
      homeId,
      moveOut: {
        $exists: false
      }
    }).fetch();
  },
  getHomeCurrentResidentIds(homeId) {
    /*
    Get all resident IDs for a given home based on residency status.
     */
    const homeCurrentResidencies = Meteor.call(
      "getHomeCurrentResidencies",
      homeId
    );

    const homeCurrentResidentIds = _.map(homeCurrentResidencies, function(
      residency
    ) {
      return residency.residentId;
    });

    return homeCurrentResidentIds;
  },
  getHomeCurrentAndActiveResidentIds: function(homeId) {
    /*
    Get all residents of specific home who have an active residency and are not on hiatus
     */
    const onHiatus = false;

    const currentResidentIds = Meteor.call("getHomeCurrentResidentIds", homeId);

    const residents = Residents.find(
      {
        _id: {
          $in: currentResidentIds
        },
        onHiatus
      },
      { sort: { firstName: 1 } }
    ).fetch();

    // Create an array containing only resident IDs
    const residentIds = _.map(residents, function(resident) {
      return resident._id;
    });

    // return the resident IDs array
    return residentIds;
  },
  getHomeCurrentAndActiveResidentCount: function(homeId) {
    // Get all current residents for specific home (not departed or on hiatus)
    const homeCurrentResidentIds = Meteor.call(
      "getHomeCurrentAndActiveResidentIds",
      homeId
    );

    // Count the length of current resident IDs list
    const homeCurrentResidentsCount = homeCurrentResidentIds.length;

    return homeCurrentResidentsCount;
  },
  getHomeActivities: function(homeId) {
    // Get all resident of this home
    var homeResidentIds = Meteor.call("getHomeCurrentResidentIds", homeId);

    // Get an array of all activity Ids for residents of this home
    var homeResidentActivitiesQuery = Activities.find(
      { residentIds: { $elemMatch: { $in: homeResidentIds } } },
      { sort: { activityDate: -1 } }
    );

    // return all activities from activity IDs array
    var homeActivities = homeResidentActivitiesQuery.fetch();

    // Create a custom activities array using collection helpers
    // for resident names, activity type, and time ago
    var homeActivitiesArray = homeActivities.map(function(activity) {
      return {
        residents: activity.residentNames(),
        type: activity.activityType(),
        duration: activity.duration,
        activityDate: activity.activityDate
      };
    });

    return homeActivitiesArray;
  },
  getHomeActivityLevelCounts: function(homeId) {
    // // Get home residents by calling getHomeResidentIds
    var residentIds = Meteor.call("getHomeCurrentAndActiveResidentIds", homeId);

    var residentActivityLevelCounts = {
      inactive: 0,
      semiActive: 0,
      active: 0
    };

    if (residentIds) {
      // Get count of recent active days for each resident
      _.each(residentIds, function(residentId) {
        const residentRecentActiveDaysCount = Meteor.call(
          "getResidentRecentActiveDaysCount",
          residentId
        );

        // Check resident activity level
        if (residentRecentActiveDaysCount === 0) {
          // If zero activities, resident is inactive
          residentActivityLevelCounts.inactive += 1;
        } else if (residentRecentActiveDaysCount < 5) {
          // If less than five activities, resident is semi-active
          // TODO: refactor to use activity threshold variable
          residentActivityLevelCounts.semiActive += 1;
        } else if (residentRecentActiveDaysCount >= 5) {
          // If greater than or equal to five activities, resident is active
          // TODO: refactor to use activity threshold variable
          residentActivityLevelCounts.active += 1;
        }
      });
    }

    return residentActivityLevelCounts;
  },
  getHomeActivityCountTrend(homeId) {
    // Get home residents
    const residentIds = Meteor.call(
      "getHomeCurrentAndActiveResidentIds",
      homeId
    );

    // Arrays for daily activity level counts
    const inactivityCounts = [];
    const semiActivityCounts = [];
    const activityCounts = [];
    const date = [];

    // Date one week ago (six days, since today counts as one day)
    const startDate = moment().subtract(6, "days");

    // Get a date object for the end of day today
    const endDate = moment().endOf("day");

    for (
      let currentDay = moment(startDate);
      currentDay.isBefore(endDate);
      currentDay.add(1, "day")
    ) {
      // Set up placeholder activity counts object
      const dailyActivityCounts = {
        date: currentDay.toDate(),
        inactive: 0,
        semiActive: 0,
        active: 0
      };

      // Get activity level for each resident
      // Compare it against the baseline
      _.each(residentIds, function(residentId) {
        const result = Meteor.call(
          "getResidentRecentActiveDaysCount",
          residentId,
          currentDay.toDate()
        );

        if (result === 0) {
          dailyActivityCounts.inactive += 1;
        } else if (result < 5 && result > 0) {
          dailyActivityCounts.semiActive += 1;
        } else if (result >= 5) {
          dailyActivityCounts.active += 1;
        }
      });

      // Add daily activity levels to activity counts arrays
      inactivityCounts.push(dailyActivityCounts.inactive);
      semiActivityCounts.push(dailyActivityCounts.semiActive);
      activityCounts.push(dailyActivityCounts.active);
      date.push(dailyActivityCounts.date);
    }

    return { activityCounts, inactivityCounts, semiActivityCounts, date };
  },
  getHomeSelectOptionsWithGroups() {
    // Get all Groups
    var groups = Groups.find().fetch();

    // Create an array of Group IDs
    var groupIDs = _.map(groups, function(group) {
      return group._id;
    });

    // Create select options for Homes input
    // Grouping homes by group
    var homeSelectOptionsWithGroups = _.map(groupIDs, function(groupId) {
      // Find the name of this group
      var groupName = Groups.findOne(groupId).name;

      // Get all homes of this group
      var groupHomes = Homes.find({ groupId: groupId }).fetch();

      // Create a homes array with name/ID pairs for label/value
      var homesOptions = _.map(groupHomes, function(groupHome) {
        // Combine resident first name and last initial
        var homeName = groupHome.name;

        // Create option for this home, with home ID as the value
        return { label: homeName, value: groupHome._id };
      });

      // Return residents and home as option group
      return { optgroup: groupName, options: homesOptions };
    });

    return homeSelectOptionsWithGroups;
  },
  getUserVisibleHomeIds(userId) {
    const userPermissions = Permissions.find({ userId }).fetch();

    const userGroups = userPermissions.map(permission => permission.groupId);

    const userVisibleHomes = Homes.find({
      groupId: { $in: userGroups }
    }).fetch();

    return userVisibleHomes.map(home => home._id);
  }
});
<|endoftext|>


<commit_before>/**
 * Simple knockout model and view model for rendering the revision table on the
 * file detail page.
 */
;(function (global, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['knockout', 'jquery', 'osfutils'], factory);
    } else {
        global.DeleteFile  = factory(ko, jQuery);
    }
}(this, function(ko, $) {
    'use strict';
    ko.punches.enableAll();
    ko.punches.attributeInterpolationMarkup.enable();

    function DeleteFileViewModel(url, delete_url) {
        var self = this;

        self.api_url = ko.observable(delete_url);
        self.files_page_url = ko.observable(null);

        $.ajax({
            url: url,
            type: 'GET',
            dataType: 'json'
        }).done(function(response) {
            self.files_page_url(response.files_page_url);
            if(self.api_url === ""){
                self.api_url(response.delete_url);
            }
        }).fail(
            $.osf.handleJSONError
        );

        self.deleteFile = function(){
            bootbox.confirm("Are you sure you want to delete this file? It will not be recoverable.",
                function(result) {
                    if (result) {
                        $('#deleting-alert').addClass('in');
                        var result = $.ajax({
                            type: 'DELETE',
                            url: self.api_url()
                        });
                        result.done(function() {
                            window.location = self.files_page_url();
                        });
                        result.fail(function( jqXHR, textStatus ) {
                            $('#deleting-alert').removeClass('in');
                            bootbox.alert( "Could not delete: " + textStatus );
                        });
                    }
                });
        };
    }
    // Public API
    function DeleteFile(selector, url, delete_url) {
        this.viewModel = new DeleteFileViewModel(url, delete_url);
        $.osf.applyBindings(this.viewModel, selector);
    }

    return DeleteFile;
}));
<commit_msg>Change docstring<commit_after>/**
 * Simple knockout model and view model for managing crud addon delete files on the
 * file detail page.
 */
;(function (global, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['knockout', 'jquery', 'osfutils'], factory);
    } else {
        global.DeleteFile  = factory(ko, jQuery);
    }
}(this, function(ko, $) {
    'use strict';
    ko.punches.enableAll();
    ko.punches.attributeInterpolationMarkup.enable();

    function DeleteFileViewModel(url, delete_url) {
        var self = this;

        self.api_url = ko.observable(delete_url);
        self.files_page_url = ko.observable(null);

        $.ajax({
            url: url,
            type: 'GET',
            dataType: 'json'
        }).done(function(response) {
            self.files_page_url(response.files_page_url);
            if(self.api_url === ""){
                self.api_url(response.delete_url);
            }
        }).fail(
            $.osf.handleJSONError
        );

        self.deleteFile = function(){
            bootbox.confirm("Are you sure you want to delete this file? It will not be recoverable.",
                function(result) {
                    if (result) {
                        $('#deleting-alert').addClass('in');
                        var result = $.ajax({
                            type: 'DELETE',
                            url: self.api_url()
                        });
                        result.done(function() {
                            window.location = self.files_page_url();
                        });
                        result.fail(function( jqXHR, textStatus ) {
                            $('#deleting-alert').removeClass('in');
                            bootbox.alert( "Could not delete: " + textStatus );
                        });
                    }
                });
        };
    }
    // Public API
    function DeleteFile(selector, url, delete_url) {
        this.viewModel = new DeleteFileViewModel(url, delete_url);
        $.osf.applyBindings(this.viewModel, selector);
    }

    return DeleteFile;
}));
<|endoftext|>


<commit_before>
"use strict";

var fs = require('fs');
var needle = require('needle');
var tinycolor = require('tinycolor2');

var conf = require('../configuration');
var log = require('../logger');
var PatternsService = require('./patternsService');

var ScriptService = {
    config: {},
    rules: [],
    ruleTimers: [],
    lastEvents:{},
    lastPatterns: {},

    start: function() {
        log.msg("ScriptService.start");
        this.config = conf.readSettings('eventServices:scriptService');
        if( !this.config ) {
            this.config = {
                type: 'script',
                enabled: false,
                maxStringLength: 200
            };
        }
        if( !this.config.enabled ) {
            log.msg("ScriptService: disabled");
            return;
        }
        this.config.maxStringLength = this.config.maxStringLength || 200;

        var allrules = conf.readSettings('eventRules') || [];
		this.rules = allrules.filter( function(r) {
            return (r.type==='script' || r.type==='file'|| r.type==='url') && r.enabled;
        });
		log.msg("ScriptService.start: rules:", this.rules);
        this.startScripts();
    },
    stop: function() {
        log.msg("ScriptService.stop");
        // stop previous timers
        this.ruleTimers.map( function(t) { clearInterval(t); } );
        this.ruleTimers = [];
        this.lastEvents = {};
    },
    reloadConfig: function() {
        this.stop();
        this.start();
    },
    startScripts: function() {
        var self = this;
        var rules = self.rules;
        log.msg("ScriptService.startScripts: rules",rules); //, "caller:",arguments.caller.toString());
        rules.map( function(rule) {
            log.msg("ScriptService.startScripts: starting ",rule);
            if( !rule.path || !rule.intervalSecs ) {
                log.error("ScriptService.startScripts: bad conf ",rule);
                return;
            }
            self.runScript(rule); //initial run
            var timer = setInterval( self.runScript.bind(self,rule), rule.intervalSecs * 1000);
            self.ruleTimers.push( timer );
        });
    },
    // FIXME: put limits in to "str" length
    // script rule:
    // {
    //   name: 'name of script'
    //   type: 'script'
    //   path: filepath to run
    //   actOnNew: true/false
    // }
    runScript: function(rule) {
        var self = this;
        log.msg("ScriptService.runScript:",rule,"timers:",self.ruleTimers);
        if( rule.type === 'script' ) {
            var spawn = require('child_process').spawn;
            var script = spawn( rule.path );
            script.stdout.on('data', function(data) {
                var str = data.toString();
                log.msg("ScriptService.runScript: str:",str,"last:",self.lastEvents[rule.name]);
                // self.handleEvent(rule,str);
                self.parse(rule,str);
            });
            script.stderr.on('data', function(data) {
                log.msg("ScriptService.runScript stderr data",data);
                log.addEvent( {type:'error', source:'script', id:rule.name, text:'stderr:'+data });
            });
            script.on('close', function(code) {
                log.msg("ScriptService.runScript close",code);
            });
        }
        else if( rule.type === 'file' ) {
            fs.readFile( rule.path, 'utf8', function(err,data) {
                // FIXME: put limit on size
                // FIXME: check for no file
                if(err) {
                    log.addEvent( {type:'error', source:'file', id:rule.name, text:err.message});
                    return;
                    // return log.error(err);
                }
                // if( self.lastEvents[rule.name] !== data ) {
                self.parse(rule,data); // NOPE
                    // self.lastEvents[rule.name] = data;
                // }
            });
        }
        else if( rule.type === 'url' ) {
            var url = rule.path;
            needle.get(url, {decode: false, parse: false}, function(err, response) {
    			// FIXME: do error handling like: net error, bad response, etc.
    			if( err ) {
                    log.msg("ScriptService.runScript: error fetching url",err, response);
                    log.addEvent( {type:'error', source:'url', id:rule.name, text:err.message });
                    return;
                }
                if( response.statusCode !== 200 ) { // badness
                    log.addEvent( {type:'error', source:'url', id:rule.name, text:response.statusMessage });
    				return;
    			}
                // otherwise continue as normal
                self.parse( rule, response.body);
            });
        }

    },

    playPattern: function(pattid,ruleid) {
        if( PatternsService.playPattern( pattid ) ) {
            this.lastPatterns[ruleid] = pattid;
            return pattid;
        }
        return false;
    },

    /**
     * Parse the output string of a script, file, or URL.
     * Plays patterns if match.
     * Sends log messages with source & id of rule.
     * Checks for the following content:
     * if 'actionType == 'parse-json', treat content as JSON,
     *   and look for 'pattern' or 'color' keys
     *   'pattern' can be meta-pattern like: '~off' and '~blink'
     * if 'actionType == 'parse-pattern', attempt to find a pattern
     *   with the "pattern:<patternname>" format, and play it.
     *   Can also use meta-patterns here.
     * Otherwise, look in text for a color string.
     *
     * @param  {Rule} rule eventRules rule for this content
     * @param  {String} str  the content to be parsed
     * @return {[type]}      [description]
     */
    parse: function(rule, str) {
        str = str.substring(0,this.config.maxStringLength);
        log.msg("ScriptService.parse:", str, "rule:",rule);
        var self = this;
        var patternre = /pattern:\s*(#*\w+)/;
        var colorre = /(#[0-9a-f]{6}|#[0-9a-f]{3}|color:\s*(.+?)\s)/i;
        var matches;

        if( self.lastEvents[rule.name] === str && rule.actOnNew ) {
            log.addEvent( {type:'info', source:rule.type, id:rule.name, text:'not modified'});
            return;
        }
        self.lastEvents[rule.name] = str;
        // log.addEvent( { type:'trigger', text:data.substring(0,40), source:rule.type, id:rule.name} );

        var actionType = rule.actionType;
        if( actionType === 'parse-json' ) {
            var json = null;
            try {
                json = JSON.parse(str);
                if( json.pattern ) {
                    // returns true on found pattern // FIXME: go back to using 'findPattern'
                    if( this.playPattern( json.pattern, rule.name ) ) {
                        log.addEvent( {type:'trigger', source:rule.type, id:rule.name, text:json.pattern});
                    }
                    else {
                        log.addEvent( {type:'error', source:rule.type, id:rule.name, text:'no pattern '+json.pattern});
                    }
                }
                else if( json.color ) {
                    var c = tinycolor(json.color);
                    if( c.isValid() ) {
                        log.addEvent( {type:'trigger', source:rule.type, id:rule.name, text:json.color});
                        this.playPattern( c.toHexString(), rule.name );
                    } else {
                        log.addEvent( {type:'error', source:rule.type, id:rule.name, text:'invalid color '+json.color});
                    }
                }
            } catch(error) {
                log.addEvent( {type:'error', source:rule.type, id:rule.name, text:error.message});
            }
        }
        else if( actionType === 'parse-pattern' ) {
            matches = patternre.exec( str );
            if( matches ) {
                var patt_name = matches[1];
                if( this.playPattern( patt_name, rule.name  ) ) {
                    log.addEvent( {type:'trigger', source:rule.type, id:rule.name, text:str});
                }
                log.addEvent( {type:'error', source:rule.type, id:rule.name, text:'no pattern '+str});
            }
            else {
                log.addEvent( {type:'error', source:rule.type, id:rule.name, text:'no pattern '+str});
            }
        }
        else { // parse-color
            matches = colorre.exec(str);
            if( matches ) {
                var colormatch = matches[2];
                var color = tinycolor( colormatch );
                if( color.isValid() ) {
                    log.addEvent( {type:'trigger', source:rule.type, id:rule.name, text:colormatch});
                    this.playPattern( color.toHexString(), rule.name );
                }
                else {
                    log.addEvent( {type:'error', source:rule.type, id:rule.name, text:'invalid color '+colormatch});
                }
            }
            else {
                log.addEvent( {type:'error', source:rule.type, id:rule.name, text:'invalid color '+str});
            }
        }
    }

};

module.exports = ScriptService;
<commit_msg>fix color search  for ScriptService<commit_after>
"use strict";

var fs = require('fs');
var needle = require('needle');
var tinycolor = require('tinycolor2');

var conf = require('../configuration');
var log = require('../logger');
var PatternsService = require('./patternsService');

var ScriptService = {
    config: {},
    rules: [],
    ruleTimers: [],
    lastEvents:{},
    lastPatterns: {},

    start: function() {
        log.msg("ScriptService.start");
        this.config = conf.readSettings('eventServices:scriptService');
        if( !this.config ) {
            this.config = {
                type: 'script',
                enabled: false,
                maxStringLength: 200
            };
        }
        if( !this.config.enabled ) {
            log.msg("ScriptService: disabled");
            return;
        }
        this.config.maxStringLength = this.config.maxStringLength || 200;

        var allrules = conf.readSettings('eventRules') || [];
		this.rules = allrules.filter( function(r) {
            return (r.type==='script' || r.type==='file'|| r.type==='url') && r.enabled;
        });
		log.msg("ScriptService.start: rules:", this.rules);
        this.startScripts();
    },
    stop: function() {
        log.msg("ScriptService.stop");
        // stop previous timers
        this.ruleTimers.map( function(t) { clearInterval(t); } );
        this.ruleTimers = [];
        this.lastEvents = {};
    },
    reloadConfig: function() {
        this.stop();
        this.start();
    },
    startScripts: function() {
        var self = this;
        var rules = self.rules;
        log.msg("ScriptService.startScripts: rules",rules); //, "caller:",arguments.caller.toString());
        rules.map( function(rule) {
            log.msg("ScriptService.startScripts: starting ",rule);
            if( !rule.path || !rule.intervalSecs ) {
                log.error("ScriptService.startScripts: bad conf ",rule);
                return;
            }
            self.runScript(rule); //initial run
            var timer = setInterval( self.runScript.bind(self,rule), rule.intervalSecs * 1000);
            self.ruleTimers.push( timer );
        });
    },
    // FIXME: put limits in to "str" length
    // script rule:
    // {
    //   name: 'name of script'
    //   type: 'script'
    //   path: filepath to run
    //   actOnNew: true/false
    // }
    runScript: function(rule) {
        var self = this;
        log.msg("ScriptService.runScript:",rule,"timers:",self.ruleTimers);
        if( rule.type === 'script' ) {
            var spawn = require('child_process').spawn;
            var script = spawn( rule.path );
            script.stdout.on('data', function(data) {
                var str = data.toString();
                log.msg("ScriptService.runScript: str:",str,"last:",self.lastEvents[rule.name]);
                // self.handleEvent(rule,str);
                self.parse(rule,str);
            });
            script.stderr.on('data', function(data) {
                log.msg("ScriptService.runScript stderr data",data);
                log.addEvent( {type:'error', source:'script', id:rule.name, text:'stderr:'+data });
            });
            script.on('close', function(code) {
                log.msg("ScriptService.runScript close",code);
            });
        }
        else if( rule.type === 'file' ) {
            fs.readFile( rule.path, 'utf8', function(err,data) {
                // FIXME: put limit on size
                // FIXME: check for no file
                if(err) {
                    log.addEvent( {type:'error', source:'file', id:rule.name, text:err.message});
                    return;
                    // return log.error(err);
                }
                // if( self.lastEvents[rule.name] !== data ) {
                self.parse(rule,data); // NOPE
                    // self.lastEvents[rule.name] = data;
                // }
            });
        }
        else if( rule.type === 'url' ) {
            var url = rule.path;
            needle.get(url, {decode: false, parse: false}, function(err, response) {
    			// FIXME: do error handling like: net error, bad response, etc.
    			if( err ) {
                    log.msg("ScriptService.runScript: error fetching url",err, response);
                    log.addEvent( {type:'error', source:'url', id:rule.name, text:err.message });
                    return;
                }
                if( response.statusCode !== 200 ) { // badness
                    log.addEvent( {type:'error', source:'url', id:rule.name, text:response.statusMessage });
    				return;
    			}
                // otherwise continue as normal
                self.parse( rule, response.body);
            });
        }

    },

    playPattern: function(pattid,ruleid) {
        if( PatternsService.playPattern( pattid ) ) {
            this.lastPatterns[ruleid] = pattid;
            return pattid;
        }
        return false;
    },

    /**
     * Parse the output string of a script, file, or URL.
     * Plays patterns if match.
     * Sends log messages with source & id of rule.
     * Checks for the following content:
     * if 'actionType == 'parse-json', treat content as JSON,
     *   and look for 'pattern' or 'color' keys
     *   'pattern' can be meta-pattern like: '~off' and '~blink'
     * if 'actionType == 'parse-pattern', attempt to find a pattern
     *   with the "pattern:<patternname>" format, and play it.
     *   Can also use meta-patterns here.
     * Otherwise, look in text for a color string.
     *
     * @param  {Rule} rule eventRules rule for this content
     * @param  {String} str  the content to be parsed
     * @return {[type]}      [description]
     */
    parse: function(rule, str) {
        str = str.substring(0,this.config.maxStringLength);
        log.msg("ScriptService.parse:", str, "rule:",rule);
        var self = this;
        var patternre = /pattern:\s*(#*\w+)/;
        var colorre = /(#[0-9a-f]{6}|#[0-9a-f]{3}|color:\s*(.+?)\s)/i;
        var matches;

        if( self.lastEvents[rule.name] === str && rule.actOnNew ) {
            log.addEvent( {type:'info', source:rule.type, id:rule.name, text:'not modified'});
            return;
        }
        self.lastEvents[rule.name] = str;
        // log.addEvent( { type:'trigger', text:data.substring(0,40), source:rule.type, id:rule.name} );

        var actionType = rule.actionType;
        if( actionType === 'parse-json' ) {
            var json = null;
            try {
                json = JSON.parse(str);
                if( json.pattern ) {
                    // returns true on found pattern // FIXME: go back to using 'findPattern'
                    if( this.playPattern( json.pattern, rule.name ) ) {
                        log.addEvent( {type:'trigger', source:rule.type, id:rule.name, text:json.pattern});
                    }
                    else {
                        log.addEvent( {type:'error', source:rule.type, id:rule.name, text:'no pattern '+json.pattern});
                    }
                }
                else if( json.color ) {
                    var c = tinycolor(json.color);
                    if( c.isValid() ) {
                        log.addEvent( {type:'trigger', source:rule.type, id:rule.name, text:json.color});
                        this.playPattern( c.toHexString(), rule.name );
                    } else {
                        log.addEvent( {type:'error', source:rule.type, id:rule.name, text:'invalid color '+json.color});
                    }
                }
            } catch(error) {
                log.addEvent( {type:'error', source:rule.type, id:rule.name, text:error.message});
            }
        }
        else if( actionType === 'parse-pattern' ) {
            matches = patternre.exec( str );
            if( matches ) {
                var patt_name = matches[1];
                if( this.playPattern( patt_name, rule.name  ) ) {
                    log.addEvent( {type:'trigger', source:rule.type, id:rule.name, text:str});
                }
                log.addEvent( {type:'error', source:rule.type, id:rule.name, text:'no pattern '+str});
            }
            else {
                log.addEvent( {type:'error', source:rule.type, id:rule.name, text:'no pattern '+str});
            }
        }
        else { // parse-color
            matches = colorre.exec(str);
            log.msg("matches:",matches);
            if( matches ) {
                var colormatch = matches[1];
                var color = tinycolor( colormatch );
                if( color.isValid() ) {
                    log.addEvent( {type:'trigger', source:rule.type, id:rule.name, text:colormatch});
                    this.playPattern( color.toHexString(), rule.name );
                }
                else {
                    log.addEvent( {type:'error', source:rule.type, id:rule.name, text:'invalid color '+colormatch});
                }
            }
            else {
                log.addEvent( {type:'error', source:rule.type, id:rule.name, text:'no color found in:'+str});
            }
        }
    }

};

module.exports = ScriptService;
<|endoftext|>