<commit_before>var async = require('async'),
keystone = require('keystone');
var exec = require('child_process').exec;

var security = keystone.security;

var ImgData = keystone.list('ImageUpload');

/**
 * List Images
 */
exports.list = function(req, res) {
        ImgData.model.find(function(err, items) {

                if (err) return res.apiError('database error', err);

                res.apiResponse({
                        collections: items
                });

        });
}

/**
 * Get Image by ID
 */
exports.get = function(req, res) {

        ImgData.model.findById(req.params.id).exec(function(err, item) {

                if (err) return res.apiError('database error', err);
                if (!item) return res.apiError('not found');

                res.apiResponse({
                        collection: item
                });

        });
}

/**
 * Update Image by ID
 */
exports.update = function(req, res) {

  //Ensure the user has a valid CSRF token
	//if (!security.csrf.validate(req)) {
	//	return res.apiError(403, 'invalid csrf');
	//}

  //Ensure the user making the request is a Keystone Admin
  //var isAdmin = req.user.get('isAdmin');
  //if(!isAdmin) {
  //  return res.apiError(403, 'Not allowed to access this API. Not Keystone Admin.');
  //}

  //Since it's possible to spoof the Keystone Admin setting in the current version of the User model,
  //This is a check to make sure the user is a ConnexstCMS Admin
  var admins = keystone.get('admins');
  var superusers = keystone.get('superusers');
  var userId = req.user.get('id');
  if((admins.indexOf(userId) == -1) && (superusers.indexOf(userId) == -1)) {
    return res.apiError(403, 'Not allowed to access this API. Not ConnextCMS Admin');
  }

  ImgData.model.findById(req.params.id).exec(function(err, item) {

    if (err) return res.apiError('database error', err);
    if (!item) return res.apiError('not found');

    var data = (req.method == 'POST') ? req.body : req.query;

    item.getUpdateHandler(req).process(data, function(err) {

      if (err) return res.apiError('create error', err);

      res.apiResponse({
              collection: item
      });

    });

  });
}

/**
 * Upload a New Image
 */
exports.create = function(req, res) {

  //Ensure the user has a valid CSRF token
	//if (!security.csrf.validate(req)) {
	//	return res.apiError(403, 'invalid csrf');
	//}

  //Ensure the user making the request is a Keystone Admin
  //var isAdmin = req.user.get('isAdmin');
  //if(!isAdmin) {
  //  return res.apiError(403, 'Not allowed to access this API. Not Keystone Admin.');
  //}

  //Since it's possible to spoof the Keystone Admin setting in the current version of the User model,
  //This is a check to make sure the user is a ConnexstCMS Admin
  var admins = keystone.get('admins');
  var superusers = keystone.get('superusers');
  var userId = req.user.get('id');
  if((admins.indexOf(userId) == -1) && (superusers.indexOf(userId) == -1)) {
    return res.apiError(403, 'Not allowed to access this API. Not ConnextCMS Admin');
  }

  var item = new ImgData.model(),
    data = (req.method == 'POST') ? req.body : req.query;

  item.getUpdateHandler(req).process(req.files, function(err) {

    if (err) return res.apiError('error', err);

    //Copy the file to the local public directory
    exec('cp ../public'+item.url+' ~/myCMS/public/uploads/images/', function(err, stdout, stderr) {
      if (err) {
        console.log('child process exited with error code ' + err.code);
        console.log('Warning: Could not copy image to local public directory. Issue with routes/api/imageupload.js/create().')
        return;
      }
      console.log(stdout);
    });

    res.apiResponse({
            image_upload: item
    });

  });
}

/**
 * Delete Image by ID
 */
exports.remove = function(req, res) {

  //Ensure the user has a valid CSRF token
	//if (!security.csrf.validate(req)) {
	//	return res.apiError(403, 'invalid csrf');
	//}

  //Ensure the user making the request is a Keystone Admin
  //var isAdmin = req.user.get('isAdmin');
  //if(!isAdmin) {
  //  return res.apiError(403, 'Not allowed to access this API. Not Keystone Admin.');
  //}

  //Since it's possible to spoof the Keystone Admin setting in the current version of the User model,
  //This is a check to make sure the user is a ConnexstCMS Admin
  var admins = keystone.get('admins');
  var superusers = keystone.get('superusers');
  var userId = req.user.get('id');
  if((admins.indexOf(userId) == -1) && (superusers.indexOf(userId) == -1)) {
    return res.apiError(403, 'Not allowed to access this API. Not ConnextCMS Admin');
  }

	var imageId = req.params.id;
	ImgData.model.findById(req.params.id).exec(function (err, item) {

		if (err) return res.apiError('database error', err);
		if (!item) return res.apiError('not found');

		item.remove(function (err) {

			if (err) return res.apiError('database error', err);

      console.log('Deleting image file: '+item.url);

      //Delete the file
      //exec('rm public/uploads/images/'+imageId+'.*', function(err, stdout, stderr) {
      exec('rm ../public'+item.url, function(err, stdout, stderr) {
        if (err) {
          console.log('child process exited with error code ' + err.code);
          console.log('Warning: Could not delete image from hard drive. Issue with routes/api/imageupload.js/remove().')
          return;
        }
        console.log(stdout);
      });

			return res.apiResponse({
				success: true
			});
		});

	});
}

<commit_msg>Changing file and image save path to Docker container persistant /public folder.<commit_after>var async = require('async'),
keystone = require('keystone');
var exec = require('child_process').exec;

var security = keystone.security;

var ImgData = keystone.list('ImageUpload');

/**
 * List Images
 */
exports.list = function(req, res) {
        ImgData.model.find(function(err, items) {

                if (err) return res.apiError('database error', err);

                res.apiResponse({
                        collections: items
                });

        });
}

/**
 * Get Image by ID
 */
exports.get = function(req, res) {

        ImgData.model.findById(req.params.id).exec(function(err, item) {

                if (err) return res.apiError('database error', err);
                if (!item) return res.apiError('not found');

                res.apiResponse({
                        collection: item
                });

        });
}

/**
 * Update Image by ID
 */
exports.update = function(req, res) {

  //Ensure the user has a valid CSRF token
	//if (!security.csrf.validate(req)) {
	//	return res.apiError(403, 'invalid csrf');
	//}

  //Ensure the user making the request is a Keystone Admin
  //var isAdmin = req.user.get('isAdmin');
  //if(!isAdmin) {
  //  return res.apiError(403, 'Not allowed to access this API. Not Keystone Admin.');
  //}

  //Since it's possible to spoof the Keystone Admin setting in the current version of the User model,
  //This is a check to make sure the user is a ConnexstCMS Admin
  var admins = keystone.get('admins');
  var superusers = keystone.get('superusers');
  var userId = req.user.get('id');
  if((admins.indexOf(userId) == -1) && (superusers.indexOf(userId) == -1)) {
    return res.apiError(403, 'Not allowed to access this API. Not ConnextCMS Admin');
  }

  ImgData.model.findById(req.params.id).exec(function(err, item) {

    if (err) return res.apiError('database error', err);
    if (!item) return res.apiError('not found');

    var data = (req.method == 'POST') ? req.body : req.query;

    item.getUpdateHandler(req).process(data, function(err) {

      if (err) return res.apiError('create error', err);

      res.apiResponse({
              collection: item
      });

    });

  });
}

/**
 * Upload a New Image
 */
exports.create = function(req, res) {

  //Ensure the user has a valid CSRF token
	//if (!security.csrf.validate(req)) {
	//	return res.apiError(403, 'invalid csrf');
	//}

  //Ensure the user making the request is a Keystone Admin
  //var isAdmin = req.user.get('isAdmin');
  //if(!isAdmin) {
  //  return res.apiError(403, 'Not allowed to access this API. Not Keystone Admin.');
  //}

  //Since it's possible to spoof the Keystone Admin setting in the current version of the User model,
  //This is a check to make sure the user is a ConnexstCMS Admin
  var admins = keystone.get('admins');
  var superusers = keystone.get('superusers');
  var userId = req.user.get('id');
  if((admins.indexOf(userId) == -1) && (superusers.indexOf(userId) == -1)) {
    return res.apiError(403, 'Not allowed to access this API. Not ConnextCMS Admin');
  }

  var item = new ImgData.model(),
    data = (req.method == 'POST') ? req.body : req.query;

  item.getUpdateHandler(req).process(req.files, function(err) {

    if (err) return res.apiError('error', err);

    console.log('item.url = '+item.url);

    //Copy the file to the local public directory
    exec('cp ~/public'+item.url+' ~/myCMS/public/uploads/images/', function(err, stdout, stderr) {
      if (err) {
        console.log('child process exited with error code ' + err.code);
        console.log('Warning: Could not copy image to local public directory. Issue with routes/api/imageupload.js/create().')
        return;
      }
      console.log(stdout);
    });

    res.apiResponse({
            image_upload: item
    });

  });
}

/**
 * Delete Image by ID
 */
exports.remove = function(req, res) {

  //Ensure the user has a valid CSRF token
	//if (!security.csrf.validate(req)) {
	//	return res.apiError(403, 'invalid csrf');
	//}

  //Ensure the user making the request is a Keystone Admin
  //var isAdmin = req.user.get('isAdmin');
  //if(!isAdmin) {
  //  return res.apiError(403, 'Not allowed to access this API. Not Keystone Admin.');
  //}

  //Since it's possible to spoof the Keystone Admin setting in the current version of the User model,
  //This is a check to make sure the user is a ConnexstCMS Admin
  var admins = keystone.get('admins');
  var superusers = keystone.get('superusers');
  var userId = req.user.get('id');
  if((admins.indexOf(userId) == -1) && (superusers.indexOf(userId) == -1)) {
    return res.apiError(403, 'Not allowed to access this API. Not ConnextCMS Admin');
  }

	var imageId = req.params.id;
	ImgData.model.findById(req.params.id).exec(function (err, item) {

		if (err) return res.apiError('database error', err);
		if (!item) return res.apiError('not found');

		item.remove(function (err) {

			if (err) return res.apiError('database error', err);

      console.log('Deleting image file: '+item.url);

      //Delete the file
      //exec('rm public/uploads/images/'+imageId+'.*', function(err, stdout, stderr) {
      exec('rm ../public'+item.url, function(err, stdout, stderr) {
        if (err) {
          console.log('child process exited with error code ' + err.code);
          console.log('Warning: Could not delete image from hard drive. Issue with routes/api/imageupload.js/remove().')
          return;
        }
        console.log(stdout);
      });

			return res.apiResponse({
				success: true
			});
		});

	});
}

<|endoftext|>


<commit_before>import React from "react";
import Router, {Route, DefaultRoute} from "react-router";
import Main from "views/Main";
import Transmit from "react-transmit";
import __fetch from "isomorphic-fetch";

var Link = Router.Link;
var RouteHandler = Router.RouteHandler;

var App = React.createClass({
	render: function () {
		return (

<Link to="app">Dashboard</Link>

<Link to="poem">Input Poem</Link>

<Link to="results">Results</Link>

					All systems operating OK

				{/* this is the important part */}
				<RouteHandler/>

		);
	}
});

var InputPoem = React.createClass({
	render: function () {
		return (

Type in your poem here...

				<form action="/poem/" method="post">
					<textarea name="poem" rows="10"></textarea>

					<button type="submit">Find acrostics!</button>
				</form>
				<RouteHandler/>

		);
	}
});

var AcrosticResults = React.createClass({
	render: function () {
		return (

Acrostic results

				<RouteHandler/>

		);
	}
});

const WrappedApp = Transmit.createContainer(App, {
	queryParams: {
		prevStargazers: [],
		nextPage: 1,
		pagesToFetch: 22
	},
	queries: {
		stargazers (queryParams) {
			/**
			 * Return a Promise of all the stargazers.
			 */
			return fetch(
				`https://api.github.com/repos/RickWong/react-isomorphic-starterkit/stargazers?per_page=100&page=${queryParams.nextPage}`
			).then((response) => {
				return response.json();
			}).then((page) => {
				if (!page || !page.length) {
					queryParams.pagesToFetch = 0;
					return queryParams.prevStargazers;
				}

				const stargazers = page.map((user) => ({
					id: user.id,
					login: user.login
				}));

				return queryParams.prevStargazers.concat(stargazers);
			});
		}
	}
});

export default (
	<Route path="/" name="app" handler={WrappedApp}>
			<Route name="poem" path="/poem/" handler={InputPoem}/>
			<Route name="results" path="/acrostics/" handler={AcrosticResults}/>
			<DefaultRoute handler={Main}/>
	</Route>
);
<commit_msg>Simplest possible react-transmit wrapper<commit_after>import React from "react";
import Router, {Route, DefaultRoute} from "react-router";
import Main from "views/Main";
import Transmit from "react-transmit";
import __fetch from "isomorphic-fetch";

var Link = Router.Link;
var RouteHandler = Router.RouteHandler;

var App = React.createClass({
	render: function () {
		return (

<Link to="app">Dashboard</Link>

<Link to="poem">Input Poem</Link>

<Link to="results">Results</Link>

					All systems operating OK

				{/* this is the important part */}
				<RouteHandler/>

		);
	}
});

var InputPoem = React.createClass({
	render: function () {
		return (

Type in your poem here...

				<form action="/poem/" method="post">
					<textarea name="poem" rows="10"></textarea>

					<button type="submit">Find acrostics!</button>
				</form>
				<RouteHandler/>

		);
	}
});

var AcrosticResults = React.createClass({
	render: function () {
		return (

Acrostic results

				<RouteHandler/>

		);
	}
});

const WrappedApp = Transmit.createContainer(App, {
	queryParams: {
		prevStargazers: [],
		nextPage: 1,
		pagesToFetch: 22
	},
	queries: {
		stargazers (queryParams) {
			return new Promise(function (resolve, reject) {
				resolve();
			});
		}
	}
});

export default (
	<Route path="/" name="app" handler={WrappedApp}>
			<Route name="poem" path="/poem/" handler={InputPoem}/>
			<Route name="results" path="/acrostics/" handler={AcrosticResults}/>
			<DefaultRoute handler={Main}/>
	</Route>
);
<|endoftext|>


<commit_before>/**
AcFun Format
Licensed Under MIT License
 An alternative format comment parser
**/
function AcfunParser(jsond){
	function fillRGB(string){
		while(string.length < 6){
			string = "0" + string;
		}
		return string;
	}
	var list = [];
	try{
		var jsondt = JSON.parse(jsond);
	}catch(e){
		console.log('Error: Could not parse json list!');
		return [];
	}
	for(var i=0;i<jsondt.length;i++){
		//Read each comment and generate a correct comment object
		var data = {};
		var xc = jsondt[i]['c'].split(',');
		if(xc.length > 0){
			data.stime = parseFloat(xc[0]) * 1000;
			data.color = '#' + fillRGB(parseInt(xc[1]).toString(16));
			data.mode = parseInt(xc[2]);
			data.size = parseInt(xc[3]);
			if(data.mode != 7){
				data.text = jsondt[i].m.replace(/(\/n|\\n|\n|\r\n|\\r)/g,"\n");
				data.text = data.text.replace(/\r/g,"\n");
				data.text = data.text.replace(/\s/g,"\u00a0");
			}else
				data.text = jsondt[i].m;
			data.hash = xc[4];
			data.date = parseInt(xc[5]);
			if(data.mode ==7){
				//High level positioned dm
				try{
					var x = JSON.parse(data.text);
				}catch(e){
					console.log('[Err] Error parsing internal data for comment');
					console.log('[Dbg] ' + data.text);
					continue;
				}
				data.text = x.n.replace(/\r/g,"\n");
				data.text = data.text.replace(/\ /g,"\u00a0");
				console.log(data.text);
				if(x.p != null){
					data.x = x.p.x;
					data.y = x.p.y;
				}else{
					data.x = 0;
					data.y = 0;
				}
				data.shadow = x.b;
				data.duration = 4000;
				if(x.l != null)
					data.duration = x.l* 1000;
				if(x.z != null){
					data.movable = true;
					data.toX = x.z[0].x;
					data.toY = x.z[0].y;
					data.moveDuration = x.z[0].l != null ? (x.z[0].l * 1000) : 500;
					data.moveDelay = 0;
				}
			}
			list.push(data);
		}
	}
	return list;
}
<commit_msg>Fixed AcFun parser to not change \r to \n<commit_after>/**
AcFun Format
Licensed Under MIT License
 An alternative format comment parser
**/
function AcfunParser(jsond){
	function fillRGB(string){
		while(string.length < 6){
			string = "0" + string;
		}
		return string;
	}
	var list = [];
	try{
		var jsondt = JSON.parse(jsond);
	}catch(e){
		console.log('Error: Could not parse json list!');
		return [];
	}
	for(var i=0;i<jsondt.length;i++){
		//Read each comment and generate a correct comment object
		var data = {};
		var xc = jsondt[i]['c'].split(',');
		if(xc.length > 0){
			data.stime = parseFloat(xc[0]) * 1000;
			data.color = '#' + fillRGB(parseInt(xc[1]).toString(16));
			data.mode = parseInt(xc[2]);
			data.size = parseInt(xc[3]);
			if(data.mode != 7){
				data.text = jsondt[i].m.replace(/(\/n|\\n|\n|\r\n|\\r)/g,"\n");
				data.text = data.text.replace(/\r/g,"\n");
				data.text = data.text.replace(/\s/g,"\u00a0");
			}else
				data.text = jsondt[i].m;
			data.hash = xc[4];
			data.date = parseInt(xc[5]);
			if(data.mode ==7){
				//High level positioned dm
				try{
					var x = JSON.parse(data.text);
				}catch(e){
					console.log('[Err] Error parsing internal data for comment');
					console.log('[Dbg] ' + data.text);
					continue;
				}
				data.text = x.n; /*.replace(/\r/g,"\n");*/
				data.text = data.text.replace(/\ /g,"\u00a0");
				console.log(data.text);
				if(x.p != null){
					data.x = x.p.x;
					data.y = x.p.y;
				}else{
					data.x = 0;
					data.y = 0;
				}
				data.shadow = x.b;
				data.duration = 4000;
				if(x.l != null)
					data.duration = x.l* 1000;
				if(x.z != null){
					data.movable = true;
					data.toX = x.z[0].x;
					data.toY = x.z[0].y;
					data.moveDuration = x.z[0].l != null ? (x.z[0].l * 1000) : 500;
					data.moveDelay = 0;
				}
			}
			list.push(data);
		}
	}
	return list;
}
<|endoftext|>


<commit_before>/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with this
 * work for additional information regarding copyright ownership. The ASF
 * licenses this file to you 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.
 */

require('views/common/table_view');

var App = require('app');
var validator = require('utils/validator');

module.exports = {

  /**
   * Launches a dialog to select hosts from the provided available hosts.
   *
   * Once the user clicks OK or Cancel, the callback is called with the
   * array of hosts (App.Host[]) selected. If the dialog was cancelled
   * or closed, null is provided to the callback. Else
   * an array (maybe empty) will be provided to the callback.
   *
   * @param initialHosts  {App.Host[]} List of hosts to pick from
   * @param selectedHosts {App.Host[]} List of hosts already selected from the available hosts
   * @param selectAtleastOneHost  {boolean} If true atleast one host has to be selected
   * @param validComponents {App.HostComponent[]} List of host-component types to pick from.
   * @param callback  Callback function which is invoked when dialog
   * @param popupDescription {Object} Consist header and message for popup
   *   Example: {header: 'header', dialogMessage: 'message'}
   *  is closed, cancelled or OK is pressed.
   */
  launchHostsSelectionDialog : function(initialHosts, selectedHosts,
      selectAtleastOneHost, validComponents, callback, popupDescription) {
    // set default popup description
    var defaultPopupDescription = {
      header: Em.I18n.t('hosts.selectHostsDialog.title'),
      dialogMessage: Em.I18n.t('hosts.selectHostsDialog.message')
    };
    if (popupDescription !== null) {
      popupDescription = $.extend(true, defaultPopupDescription, popupDescription);
    }
    App.ModalPopup.show({

      classNames: ['common-modal-wrapper', 'full-height-modal'],
      modalDialogClasses: ['modal-lg'],

      elementId: 'host-selection-dialog',

      header: popupDescription.header,

      dialogMessage: popupDescription.dialogMessage,

      warningMessage: null,

      availableHosts: [],

      onPrimary: function () {
        this.set('warningMessage', null);
        var arrayOfSelectedHosts = this.get('availableHosts').filterProperty('selected', true).mapProperty('host.id');
        if (selectAtleastOneHost && arrayOfSelectedHosts.length < 1) {
          this.set('warningMessage', Em.I18n.t('hosts.selectHostsDialog.message.warning'));
          return;
        }
        callback(arrayOfSelectedHosts);
        this.hide();
      },

      disablePrimary: Em.computed.not('isLoaded'),

      onSecondary: function () {
        callback(null);
        this.hide();
      },

      bodyClass: App.TableView.extend({

        templateName: require('templates/common/configs/overrideWindow'),

        controllerBinding: 'App.router.mainServiceInfoConfigsController',

        isPaginate: true,

        filteredContent: [],

        filteredContentObs: function() {
          Em.run.once(this, this.filteredContentObsOnce);
        }.observes('parentView.availableHosts.@each.filtered'),

        filteredContentObsOnce: function() {
          var filteredContent = this.get('parentView.availableHosts').filterProperty('filtered') || [];
          this.set('filteredContent', filteredContent);
        },

        filterText: '',

        filterTextPlaceholder: Em.I18n.t('hosts.selectHostsDialog.filter.placeHolder'),

        filterColumn: null,

        filterColumns: Ember.A([
          Ember.Object.create({id: 'ip', name: 'IP Address', selected: true}),
          Ember.Object.create({id: 'cpu', name: 'CPU', selected: false}),
          Ember.Object.create({id: 'memory', name: 'RAM', selected: false}),
          Ember.Object.create({id: 'osArch', name: 'OS Architecture', selected: false}),
          Ember.Object.create({id: 'osType', name: 'OS Type', selected: false}),
          Ember.Object.create({id: 'diskTotal', name: 'Total Disks Capacity', selected: false}),
          Ember.Object.create({id: 'disksMounted', name: '# of Disk Mounts', selected: false})
        ]),

        showOnlySelectedHosts: false,

        filterComponents: validComponents,

        filterComponent: null,

        isDisabled: Em.computed.not('parentView.isLoaded'),

        didInsertElement: function() {
          var defaultFilterColumn = this.get('filterColumns').findProperty('selected');
          this.set('filterColumn', defaultFilterColumn);
          initialHosts.setEach('filtered', true);
          this.set('parentView.availableHosts', initialHosts);
          this.set('parentView.isLoaded', true);
          this.filteredContentObsOnce();
        },

        /**
         * Default filter-method isn't needed
         */
        filter: Em.K,

        filterHosts: function () {
          var filterText = this.get('filterText');
          var showOnlySelectedHosts = this.get('showOnlySelectedHosts');
          var filterComponent = this.get('filterComponent');
          var filterColumn = this.get('filterColumn');

          this.get('parentView.availableHosts').forEach(function (host) {
            var skip = showOnlySelectedHosts && !host.get('selected');
            var value = host.get('host').get(filterColumn.id);
            var hostComponentNames = host.get('hostComponentNames');

            host.set('filterColumnValue', value);

            if (!skip && filterText && (value == null || !value.toString().match(filterText)) && !host.get('host.publicHostName').match(filterText)) {
              skip = true;
            }
            if (!skip && filterComponent && hostComponentNames.length > 0) {
              skip = !hostComponentNames.contains(filterComponent.get('componentName'));
            }
            host.set('filtered', !skip);
          }, this);

          this.set('startIndex', this.get('parentView.availableHosts').someProperty('filtered') ? 1 : 0);
        }.observes('parentView.availableHosts', 'filterColumn', 'filterText', 'filterComponent', 'filterComponent.componentName', 'showOnlySelectedHosts'),

        hostSelectMessage: function () {
          var hosts = this.get('parentView.availableHosts');
          var selectedHosts = hosts.filterProperty('selected', true);
          return this.t('hosts.selectHostsDialog.selectedHostsLink').format(selectedHosts.get('length'), hosts.get('length'))
        }.property('parentView.availableHosts.@each.selected'),

        selectFilterColumn: function (event) {
          if (event != null && event.context != null && event.context.id != null) {
            var filterColumn = this.get('filterColumn');
            if (filterColumn != null) {
              filterColumn.set('selected', false);
            }
            event.context.set('selected', true);
            this.set('filterColumn', event.context);
          }
        },

        selectFilterComponent: function (event) {
          if (event != null && event.context != null && event.context.componentName != null) {
            var currentFilter = this.get('filterComponent');
            if (currentFilter != null) {
              currentFilter.set('selected', false);
            }
            if (currentFilter != null && currentFilter.componentName === event.context.componentName) {
              // selecting the same filter deselects it.
              this.set('filterComponent', null);
            } else {
              this.set('filterComponent', event.context);
              event.context.set('selected', true);
            }
          }
        },

        allHostsSelected: false,

        toggleSelectAllHosts: function (event) {
          this.get('parentView.availableHosts').filterProperty('filtered').setEach('selected', this.get('allHostsSelected'));
        }.observes('allHostsSelected'),

        toggleShowSelectedHosts: function () {
          var currentFilter = this.get('filterComponent');
          if (currentFilter != null) {
            currentFilter.set('selected', false);
          }
          this.setProperties({
            filterComponent: null,
            filterText: null
          });
          this.toggleProperty('showOnlySelectedHosts');
        }
      })
    });
  },

   /**
   * Bulk setting of for rack id
   * @param {Object} operationData - data about bulk operation (action, hostComponents etc)
   * @param {Ember.Enumerable} hosts - list of affected hosts
   */
  setRackInfo: function (operationData, hosts, rackId) {
    var self = this;
    var hostNames = hosts.mapProperty('hostName');
    return App.ModalPopup.show({
      header: Em.I18n.t('hosts.host.details.setRackId'),
      disablePrimary: true,
      rackId: rackId ? rackId : "",
      bodyClass: Em.View.extend({
        templateName: require('templates/main/host/rack_id_popup'),
        errorMessage: null,
        isValid: true,
        validation: function () {
          this.set('isValid', validator.isValidRackId(this.get('parentView.rackId')));
          this.set('errorMessage', this.get('isValid') ? '' : Em.I18n.t('hostPopup.setRackId.invalid'));
          this.set('parentView.disablePrimary', !this.get('isValid'));
        }.observes('parentView.rackId')
      }),
      onPrimary: function() {
        var rackId = this.get('rackId');
        if (hostNames.length) {
          App.ajax.send({
            name: 'bulk_request.hosts.update_rack_id',
            sender: self,
            data: {
              hostNames: hostNames.join(','),
              requestInfo: operationData.message,
              rackId: rackId,
              hostNamesArray: hostNames
            },
            success: 'successRackId',
            error: 'errorRackId'
          });
        }
        this.hide();
      }
    });
  },

  /**
   * Success callback for set rack id request
   */
  successRackId: function (response, request, params) {
    App.Host.find().forEach(function(host){
      if (params.hostNamesArray.contains(host.get('hostName'))) {
        host.set('rack', params.rackId)
      }
    });
  },

  /**
   * Warn user that the rack id will not be updated
   */
  errorRackId: function () {
    App.showAlertPopup(Em.I18n.t('common.error'), Em.I18n.t('hostPopup.setRackId.error'));
  }
};
<commit_msg>AMBARI-21962 Components Filter has a preselected component from a deleted conf group while adding hosts to a new conf group. Hosts listed are not correct either (Ishan Bhatt via atkach)<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with this
 * work for additional information regarding copyright ownership. The ASF
 * licenses this file to you 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.
 */

require('views/common/table_view');

var App = require('app');
var validator = require('utils/validator');

module.exports = {

  /**
   * Launches a dialog to select hosts from the provided available hosts.
   *
   * Once the user clicks OK or Cancel, the callback is called with the
   * array of hosts (App.Host[]) selected. If the dialog was cancelled
   * or closed, null is provided to the callback. Else
   * an array (maybe empty) will be provided to the callback.
   *
   * @param initialHosts  {App.Host[]} List of hosts to pick from
   * @param selectedHosts {App.Host[]} List of hosts already selected from the available hosts
   * @param selectAtleastOneHost  {boolean} If true atleast one host has to be selected
   * @param validComponents {App.HostComponent[]} List of host-component types to pick from.
   * @param callback  Callback function which is invoked when dialog
   * @param popupDescription {Object} Consist header and message for popup
   *   Example: {header: 'header', dialogMessage: 'message'}
   *  is closed, cancelled or OK is pressed.
   */
  launchHostsSelectionDialog : function(initialHosts, selectedHosts,
      selectAtleastOneHost, validComponents, callback, popupDescription) {
    // set default popup description
    var defaultPopupDescription = {
      header: Em.I18n.t('hosts.selectHostsDialog.title'),
      dialogMessage: Em.I18n.t('hosts.selectHostsDialog.message')
    };
    if (popupDescription !== null) {
      popupDescription = $.extend(true, defaultPopupDescription, popupDescription);
    }
    App.ModalPopup.show({

      classNames: ['common-modal-wrapper', 'full-height-modal'],
      modalDialogClasses: ['modal-lg'],

      elementId: 'host-selection-dialog',

      header: popupDescription.header,

      dialogMessage: popupDescription.dialogMessage,

      warningMessage: null,

      availableHosts: [],

      onPrimary: function () {
        this.set('warningMessage', null);
        var arrayOfSelectedHosts = this.get('availableHosts').filterProperty('selected', true).mapProperty('host.id');
        if (selectAtleastOneHost && arrayOfSelectedHosts.length < 1) {
          this.set('warningMessage', Em.I18n.t('hosts.selectHostsDialog.message.warning'));
          return;
        }
        callback(arrayOfSelectedHosts);
        this.hide();
      },

      disablePrimary: Em.computed.not('isLoaded'),

      onSecondary: function () {
        callback(null);
        this.hide();
      },

      bodyClass: App.TableView.extend({

        templateName: require('templates/common/configs/overrideWindow'),

        controllerBinding: 'App.router.mainServiceInfoConfigsController',

        isPaginate: true,

        filteredContent: [],

        filteredContentObs: function() {
          Em.run.once(this, this.filteredContentObsOnce);
        }.observes('parentView.availableHosts.@each.filtered'),

        filteredContentObsOnce: function() {
          var filteredContent = this.get('parentView.availableHosts').filterProperty('filtered') || [];
          this.set('filteredContent', filteredContent);
        },

        filterText: '',

        filterTextPlaceholder: Em.I18n.t('hosts.selectHostsDialog.filter.placeHolder'),

        filterColumn: null,

        filterColumns: Ember.A([
          Ember.Object.create({id: 'ip', name: 'IP Address', selected: true}),
          Ember.Object.create({id: 'cpu', name: 'CPU', selected: false}),
          Ember.Object.create({id: 'memory', name: 'RAM', selected: false}),
          Ember.Object.create({id: 'osArch', name: 'OS Architecture', selected: false}),
          Ember.Object.create({id: 'osType', name: 'OS Type', selected: false}),
          Ember.Object.create({id: 'diskTotal', name: 'Total Disks Capacity', selected: false}),
          Ember.Object.create({id: 'disksMounted', name: '# of Disk Mounts', selected: false})
        ]),

        showOnlySelectedHosts: false,

        filterComponents: validComponents,

        filterComponent: null,

        isDisabled: Em.computed.not('parentView.isLoaded'),

        didInsertElement: function() {
          var defaultFilterColumn = this.get('filterColumns').findProperty('selected');
          this.get('filterComponents').setEach('selected', false);
          this.set('filterColumn', defaultFilterColumn);
          initialHosts.setEach('filtered', true);
          this.set('parentView.availableHosts', initialHosts);
          this.set('parentView.isLoaded', true);
          this.filteredContentObsOnce();
        },

        /**
         * Default filter-method isn't needed
         */
        filter: Em.K,

        filterHosts: function () {
          var filterText = this.get('filterText');
          var showOnlySelectedHosts = this.get('showOnlySelectedHosts');
          var filterComponent = this.get('filterComponent');
          var filterColumn = this.get('filterColumn');

          this.get('parentView.availableHosts').forEach(function (host) {
            var skip = showOnlySelectedHosts && !host.get('selected');
            var value = host.get('host').get(filterColumn.id);
            var hostComponentNames = host.get('hostComponentNames');

            host.set('filterColumnValue', value);

            if (!skip && filterText && (value == null || !value.toString().match(filterText)) && !host.get('host.publicHostName').match(filterText)) {
              skip = true;
            }
            if (!skip && filterComponent && hostComponentNames.length > 0) {
              skip = !hostComponentNames.contains(filterComponent.get('componentName'));
            }
            host.set('filtered', !skip);
          }, this);

          this.set('startIndex', this.get('parentView.availableHosts').someProperty('filtered') ? 1 : 0);
        }.observes('parentView.availableHosts', 'filterColumn', 'filterText', 'filterComponent', 'filterComponent.componentName', 'showOnlySelectedHosts'),

        hostSelectMessage: function () {
          var hosts = this.get('parentView.availableHosts');
          var selectedHosts = hosts.filterProperty('selected', true);
          return this.t('hosts.selectHostsDialog.selectedHostsLink').format(selectedHosts.get('length'), hosts.get('length'))
        }.property('parentView.availableHosts.@each.selected'),

        selectFilterColumn: function (event) {
          if (event != null && event.context != null && event.context.id != null) {
            var filterColumn = this.get('filterColumn');
            if (filterColumn != null) {
              filterColumn.set('selected', false);
            }
            event.context.set('selected', true);
            this.set('filterColumn', event.context);
          }
        },

        selectFilterComponent: function (event) {
          if (event != null && event.context != null && event.context.componentName != null) {
            var currentFilter = this.get('filterComponent');
            if (currentFilter != null) {
              currentFilter.set('selected', false);
            }
            if (currentFilter != null && currentFilter.componentName === event.context.componentName) {
              // selecting the same filter deselects it.
              this.set('filterComponent', null);
            } else {
              this.set('filterComponent', event.context);
              event.context.set('selected', true);
            }
          }
        },

        allHostsSelected: false,

        toggleSelectAllHosts: function (event) {
          this.get('parentView.availableHosts').filterProperty('filtered').setEach('selected', this.get('allHostsSelected'));
        }.observes('allHostsSelected'),

        toggleShowSelectedHosts: function () {
          var currentFilter = this.get('filterComponent');
          if (currentFilter != null) {
            currentFilter.set('selected', false);
          }
          this.setProperties({
            filterComponent: null,
            filterText: null
          });
          this.toggleProperty('showOnlySelectedHosts');
        }
      })
    });
  },

   /**
   * Bulk setting of for rack id
   * @param {Object} operationData - data about bulk operation (action, hostComponents etc)
   * @param {Ember.Enumerable} hosts - list of affected hosts
   */
  setRackInfo: function (operationData, hosts, rackId) {
    var self = this;
    var hostNames = hosts.mapProperty('hostName');
    return App.ModalPopup.show({
      header: Em.I18n.t('hosts.host.details.setRackId'),
      disablePrimary: true,
      rackId: rackId ? rackId : "",
      bodyClass: Em.View.extend({
        templateName: require('templates/main/host/rack_id_popup'),
        errorMessage: null,
        isValid: true,
        validation: function () {
          this.set('isValid', validator.isValidRackId(this.get('parentView.rackId')));
          this.set('errorMessage', this.get('isValid') ? '' : Em.I18n.t('hostPopup.setRackId.invalid'));
          this.set('parentView.disablePrimary', !this.get('isValid'));
        }.observes('parentView.rackId')
      }),
      onPrimary: function() {
        var rackId = this.get('rackId');
        if (hostNames.length) {
          App.ajax.send({
            name: 'bulk_request.hosts.update_rack_id',
            sender: self,
            data: {
              hostNames: hostNames.join(','),
              requestInfo: operationData.message,
              rackId: rackId,
              hostNamesArray: hostNames
            },
            success: 'successRackId',
            error: 'errorRackId'
          });
        }
        this.hide();
      }
    });
  },

  /**
   * Success callback for set rack id request
   */
  successRackId: function (response, request, params) {
    App.Host.find().forEach(function(host){
      if (params.hostNamesArray.contains(host.get('hostName'))) {
        host.set('rack', params.rackId)
      }
    });
  },

  /**
   * Warn user that the rack id will not be updated
   */
  errorRackId: function () {
    App.showAlertPopup(Em.I18n.t('common.error'), Em.I18n.t('hostPopup.setRackId.error'));
  }
};
<|endoftext|>


<commit_before>var bus = require('./bus'),
    _ = require('lodash'),
    request = require('request'),
    mongoose = require('mongoose'),
    Service = mongoose.model('Service');

function init(){
    bus.on(bus.list.REGISTRY.CACHED, function(registry){
        Service.find({type: 'tracking'}).lean().exec().then(function(services){
            _.each(services, function(service){
                trackingServiceHandler(service, registry)
            });
        });
    });
}

function trackingServiceHandler(service, registry){
    request(service.url + 'stats', function(error, response, body){
        if (error){
            console.error(error);
            return;
        }

        var stats = JSON.parse(body),
            statsReg = {};

        _.each(stats, function(stat){
            if (stat.online && stat.MaxUsers){
                statsReg[stat.name] = stat;
            }
        });

        _.each(registry, function(extension){
            if (!statsReg[extension._id]) { return true; }
            if (!extension.online) { extension.online = 0; }
            if (!extension.maxUsers) { extension.maxUsers = 0; }

            extension.online += statsReg.online;
            extension.maxUsers += statsReg.maxUsers;
        });
    });
}

exports.init = init;
<commit_msg>fixes to make last commit work<commit_after>var bus = require('./bus'),
    _ = require('lodash'),
    request = require('request'),
    mongoose = require('mongoose'),
    Service = mongoose.model('Service'),
    CacheService = require('../services/cache');

function init(){
    bus.on(bus.list.REGISTRY.CACHED, function(){
        var registry = CacheService.get('registry');

        Service.find({type: 'tracking'}).lean().exec().then(function(services){
            _.each(services, function(service){
                trackingServiceHandler(service, registry)
            });
        });
    });
}

function trackingServiceHandler(service, registry){
    request(service.url + 'stats', function(error, response, body){
        if (error){
            console.error(error);
            return;
        }

        var stats = JSON.parse(body),
            statsReg = {};

        _.each(stats, function(stat){

            if (stat.online && stat.maxUsers){
                statsReg[stat.name] = stat;
            }
        });

        _.each(registry, function(extension){
            if (!statsReg[extension._id]) { return true; }

            console.log(extension._id);

            if (!extension.online) { extension.online = 0; }
            if (!extension.maxUsers) { extension.maxUsers = 0; }

            extension.online += statsReg[extension._id].online;
            extension.maxUsers += statsReg[extension._id].maxUsers;
        });
    });
}

exports.init = init;
<|endoftext|>


<commit_before>var $ = require('jQuery');
var onReady = require('kwf/on-ready');
var gmapLoader = require('kwf/google-map/loader');
var gmapMap = require('kwf/google-map/map');

var renderedMaps = [];

var renderMap = function(el) {
    if ($.inArray(el, renderedMaps) != -1) return;
    renderedMaps.push(el);

    var cfg = el.find(".options", true);
    if (!cfg.length) return;
    cfg = $.parseJSON(cfg.val());

    var text = el.find("div.text");
    cfg.mapContainer = el;
    if (!cfg.markers) {
        cfg.markers = {
            longitude : cfg.longitude,
            latitude  : cfg.latitude,
            autoOpenInfoWindow: cfg.autoOpenInfoWindow
        };
        if (text.length) cfg.markers.infoHtml = text.html();
    }

    var cls = eval(cfg.mapClass) || gmapMap;
    var myMap = new cls(cfg);
    el.map = myMap;

    gmapLoader(function() {
        this.show();
    }, myMap);

    return myMap;
};

onReady.onRender('.kwcClass', function(el) {
    if (!el.get('gmapObject') && !el.is(':hidden')) {
        el.data('gmapObject', renderMap(el));
    }
}, { checkVisibility: true });

onReady.onRender('.kwcClass', function (el) {
    var mobileOverlayOpen = el.find('.mobileOverlayOpen');
    var mobileOverlayClose = el.find('.mobileOverlayClose');

    mobileOverlayOpen.click(function (ev) {
        if ($(this).is(':visible')) {
            var newEl = $(this).parent();
            newEl.addClass('navigate');
            $('html, body').animate({
                scrollTop: newEl.offset().top
            }, 400, function() {
                google.maps.event.trigger(newEl.data('gmapObject').gmap, 'resize');
            });
        }
    });

    mobileOverlayClose.on('touchstart click', function (ev) {
        if ($(this).is(':visible')) {
            var newEl = $(this).parent();
            newEl.removeClass('navigate');
            $('html, body').animate({
                scrollTop: newEl.offset().top - (($(window).innerHeight() - newEl.height()) / 2)
            });
        }
    });
});
<commit_msg>GoogleMap: remove renderdMaps and check for is:hidden<commit_after>var $ = require('jQuery');
var onReady = require('kwf/on-ready');
var gmapLoader = require('kwf/google-map/loader');
var gmapMap = require('kwf/google-map/map');

var renderMap = function(el) {

    var cfg = el.find(".options", true);
    if (!cfg.length) return;
    cfg = $.parseJSON(cfg.val());

    var text = el.find("div.text");
    cfg.mapContainer = el;
    if (!cfg.markers) {
        cfg.markers = {
            longitude : cfg.longitude,
            latitude  : cfg.latitude,
            autoOpenInfoWindow: cfg.autoOpenInfoWindow
        };
        if (text.length) cfg.markers.infoHtml = text.html();
    }

    var cls = eval(cfg.mapClass) || gmapMap;
    var myMap = new cls(cfg);

    gmapLoader(function() {
        this.show();
    }, myMap);

    return myMap;
};

onReady.onRender('.kwcClass', function(el) {
    el.data('gmapObject', renderMap(el));
}, { checkVisibility: true });

onReady.onRender('.kwcClass', function (el) {
    var mobileOverlayOpen = el.find('.mobileOverlayOpen');
    var mobileOverlayClose = el.find('.mobileOverlayClose');

    mobileOverlayOpen.click(function (ev) {
        if ($(this).is(':visible')) {
            var newEl = $(this).parent();
            newEl.addClass('navigate');
            $('html, body').animate({
                scrollTop: newEl.offset().top
            }, 400, function() {
                google.maps.event.trigger(newEl.data('gmapObject').gmap, 'resize');
            });
        }
    });

    mobileOverlayClose.on('touchstart click', function (ev) {
        if ($(this).is(':visible')) {
            var newEl = $(this).parent();
            newEl.removeClass('navigate');
            $('html, body').animate({
                scrollTop: newEl.offset().top - (($(window).innerHeight() - newEl.height()) / 2)
            });
        }
    });
});
<|endoftext|>


<commit_before>/**
 * Helper "class" for accessing MediaWiki API and handling cookie-based session
 */
module.exports = (function() {
	'use strict';

	// introspect package.json to get module version
	var VERSION = require('../package').version;

	var
		// @see https://github.com/caolan/async
		async = require('async'),
		// @see https://github.com/mikeal/request
		request = require('request');

	var api = function(options) {
		this.protocol = options.protocol || 'http';
		this.port = options.port || (this.protocol === 'https' ? 443 : undefined /* will default to 80 */);
		this.server = options.server;
		this.path = options.path;
		this.proxy = options.proxy;
		this.jar = request.jar(); // create new cookie jar for each instance

		this.debug = options.debug;

		// set up logging
		var winston = require('winston');
		this.logger = new (winston.Logger)();

		// console logging
		if (this.debug) {
			this.logger.add(winston.transports.Console, {
				level: 'debug'
			});
		}

		this.logger.cli();

		// file logging
		var path = require('path'),
			fs = require('fs'),
			logDir = path.dirname(process.argv[1]) + '/log/',
			logFile = logDir + path.basename(process.argv[1], '.js') + '.log';

		if (fs.existsSync(logDir)) {
			this.logger.add(winston.transports.File, {
				colorize: true,
				filename: logFile,
				json: false,
				level: this.debug ? 'debug' : 'info'
			});
		}

		// requests queue
		// @see https://github.com/caolan/async#queue
		var concurrency = options.concurrency || 3; // how many tasks (i.e. requests) to run in parallel
		this.queue = async.queue(function(task, callback) {
			// process the task (and call the provided callback once it's completed)
			task(callback);
		}, concurrency);

		// HTTP client
		this.formatUrl = require('url').format;

		this.userAgent = options.userAgent || ('nodemw v' + VERSION + ' (node.js ' + process.version + '; ' + process.platform + ' ' + process.arch + ')');
		this.version = VERSION;

		// debug info
		this.info(process.argv.join(' '));
		this.info(this.userAgent);
		this.info('Using <' + this.protocol + '://' + this.server + ':' + this.port + this.path + '/api.php> as API entry point');
		this.info('----');
	};

	var doRequest = function(params, callback, method, done) {
		var self = this,
			// store requested action - will be used when parsing a response
			actionName = params.action,
			// "request" options
			options = {
				method: method || 'GET',
				proxy: this.proxy || false,
				jar: this.jar,
				headers: {
					'User-Agent': this.userAgent
				}
			};

		// HTTP request parameters
		params = params || {};

		// force JSON format
		params.format = 'json';

		// handle uploads
		if (method === 'UPLOAD') {
			options.method = 'POST';

			var CRLF = "\r\n",
				postBody = [],
				boundary = 'nodemw' + Math.random().toString().substr(2);

			// encode each field
			Object.keys(params).forEach(function(fieldName) {
				var value = params[fieldName];

				postBody.push("--" + boundary);
				postBody.push(CRLF);

				if (typeof value === 'string') {
					// properly encode UTF8 in binary-safe POST data
					postBody.push("Content-Disposition: form-data; name=\"" + fieldName + "\"");
					postBody.push(CRLF);
					postBody.push(CRLF);
					postBody.push(new Buffer(value, 'utf8'));
				}
				else {
					// send attachment
					postBody.push("Content-Disposition: form-data; name=\"" + fieldName + "\"; filename=\"foo\"");
					postBody.push(CRLF);
					postBody.push(CRLF);
					postBody.push(value);
				}

				postBody.push(CRLF);
			});

			postBody.push("--" + boundary + "--");

			// encode post data
			options.headers['content-type'] = 'multipart/form-data; boundary=' + boundary;
			options.body = postBody;

			params = {};
		}

		// form an URL to API
		options.url = this.formatUrl({
			protocol: this.protocol,
			port: this.port,
			hostname: this.server,
			pathname: this.path + '/api.php',
			query: (options.method === 'GET') ? params : {}
		});

		// POST all parameters (avoid "request string too long" errors)
		if (method === 'POST') {
			options.form = params;
		}

		this.logger.debug('API action: %s', actionName);
		this.logger.debug('%s <%s>', options.method, options.url);
		if (options.form) {
			this.logger.debug('POST fields: %s', Object.keys(options.form).join(', '));
		}

		request(options, function(error, response, body) {
			response = response || {};

			if (error) {
				self.logger.error('Request to API failed: %s', error);
				callback(new Error('Request to API failed: ' + error));
				done();
				return;
			}

			if (response.statusCode !== 200) {
				self.logger.error('Request to API failed: HTTP status code was %d for <%s>', response.statusCode || 'unknown', options.url);
				self.logger.data(new Error().stack);

				callback(new Error('Request to API failed: HTTP status code was ' + response.statusCode));
				done();
				return;
			}

			// parse response
			var data,
				info,
				next;

			try {
				data = JSON.parse(body);
				info = data && data[actionName];
				next = data && data['query-continue'] && data['query-continue'][params.list];
			}
			catch(e) {
				self.logger.error('Error parsing JSON response: %s',  body);
				self.logger.data(body);

				callback(new Error('Error parsing JSON response'));
				done();
				return;
			}

			//if (!callback) data.error = {info: 'foo'}; // debug

			if (data && !data.error && info) {
				if (next) {
					self.logger.debug("There's more data");
					self.logger.debug(next);
				}

				callback(null, info, next, data);
			}
			else {
				self.logger.error('Error returned by API: %s',  data.error.info);
				self.logger.data(data.error);

				callback(new Error('Error returned by API: ' + data.error.info));
			}
			done();
		});
	};

	// public interface
	api.prototype = {
		log: function() {
			this.logger.log.apply(this.logger, arguments);
		},

		info: function() {
			this.logger.info.apply(this.logger, arguments);
		},

		warn: function(msg, extra) {
			this.logger.warn.apply(this.logger, arguments);
		},

		error: function(msg, extra) {
			this.logger.error.apply(this.logger, arguments);
		},

		// adds request to the queue
		call: function(params, callback, method) {
			this.queue.push(function(done) {
				doRequest.apply(this, [params, callback, method, done]);
			}.bind(this));
		},

		// fetch an external resource
		fetchUrl: function(url, callback, encoding) {
			var self = this;
			encoding = encoding || 'utf-8';

			// add a request to the queue
			this.queue.push(function(done) {
				self.info("Fetching <%s> (as %s)...", url, encoding);

				request({
					url: url,
					encoding: (encoding === 'binary') ? null : encoding
				}, function (error, response, body) {
					if (!error && response.statusCode === 200) {
						self.info('<%s>: fetched %s kB', url, (body.length/1024).toFixed(2));
						callback(null, body);
					}
					else {
						if (!error) {
							error = new Error('HTTP status '+ response.statusCode);
						}

						self.error('Failed to fetch <' + url + '>');
						self.error(error.message);
						callback(error, body);
					}

					done();
				});
			});
		}
	};

	return api;
}());
<commit_msg>only show error if data.error, info is optional<commit_after>/**
 * Helper "class" for accessing MediaWiki API and handling cookie-based session
 */
module.exports = (function() {
	'use strict';

	// introspect package.json to get module version
	var VERSION = require('../package').version;

	var
		// @see https://github.com/caolan/async
		async = require('async'),
		// @see https://github.com/mikeal/request
		request = require('request');

	var api = function(options) {
		this.protocol = options.protocol || 'http';
		this.port = options.port || (this.protocol === 'https' ? 443 : undefined /* will default to 80 */);
		this.server = options.server;
		this.path = options.path;
		this.proxy = options.proxy;
		this.jar = request.jar(); // create new cookie jar for each instance

		this.debug = options.debug;

		// set up logging
		var winston = require('winston');
		this.logger = new (winston.Logger)();

		// console logging
		if (this.debug) {
			this.logger.add(winston.transports.Console, {
				level: 'debug'
			});
		}

		this.logger.cli();

		// file logging
		var path = require('path'),
			fs = require('fs'),
			logDir = path.dirname(process.argv[1]) + '/log/',
			logFile = logDir + path.basename(process.argv[1], '.js') + '.log';

		if (fs.existsSync(logDir)) {
			this.logger.add(winston.transports.File, {
				colorize: true,
				filename: logFile,
				json: false,
				level: this.debug ? 'debug' : 'info'
			});
		}

		// requests queue
		// @see https://github.com/caolan/async#queue
		var concurrency = options.concurrency || 3; // how many tasks (i.e. requests) to run in parallel
		this.queue = async.queue(function(task, callback) {
			// process the task (and call the provided callback once it's completed)
			task(callback);
		}, concurrency);

		// HTTP client
		this.formatUrl = require('url').format;

		this.userAgent = options.userAgent || ('nodemw v' + VERSION + ' (node.js ' + process.version + '; ' + process.platform + ' ' + process.arch + ')');
		this.version = VERSION;

		// debug info
		this.info(process.argv.join(' '));
		this.info(this.userAgent);
		this.info('Using <' + this.protocol + '://' + this.server + ':' + this.port + this.path + '/api.php> as API entry point');
		this.info('----');
	};

	var doRequest = function(params, callback, method, done) {
		var self = this,
			// store requested action - will be used when parsing a response
			actionName = params.action,
			// "request" options
			options = {
				method: method || 'GET',
				proxy: this.proxy || false,
				jar: this.jar,
				headers: {
					'User-Agent': this.userAgent
				}
			};

		// HTTP request parameters
		params = params || {};

		// force JSON format
		params.format = 'json';

		// handle uploads
		if (method === 'UPLOAD') {
			options.method = 'POST';

			var CRLF = "\r\n",
				postBody = [],
				boundary = 'nodemw' + Math.random().toString().substr(2);

			// encode each field
			Object.keys(params).forEach(function(fieldName) {
				var value = params[fieldName];

				postBody.push("--" + boundary);
				postBody.push(CRLF);

				if (typeof value === 'string') {
					// properly encode UTF8 in binary-safe POST data
					postBody.push("Content-Disposition: form-data; name=\"" + fieldName + "\"");
					postBody.push(CRLF);
					postBody.push(CRLF);
					postBody.push(new Buffer(value, 'utf8'));
				}
				else {
					// send attachment
					postBody.push("Content-Disposition: form-data; name=\"" + fieldName + "\"; filename=\"foo\"");
					postBody.push(CRLF);
					postBody.push(CRLF);
					postBody.push(value);
				}

				postBody.push(CRLF);
			});

			postBody.push("--" + boundary + "--");

			// encode post data
			options.headers['content-type'] = 'multipart/form-data; boundary=' + boundary;
			options.body = postBody;

			params = {};
		}

		// form an URL to API
		options.url = this.formatUrl({
			protocol: this.protocol,
			port: this.port,
			hostname: this.server,
			pathname: this.path + '/api.php',
			query: (options.method === 'GET') ? params : {}
		});

		// POST all parameters (avoid "request string too long" errors)
		if (method === 'POST') {
			options.form = params;
		}

		this.logger.debug('API action: %s', actionName);
		this.logger.debug('%s <%s>', options.method, options.url);
		if (options.form) {
			this.logger.debug('POST fields: %s', Object.keys(options.form).join(', '));
		}

		request(options, function(error, response, body) {
			response = response || {};

			if (error) {
				self.logger.error('Request to API failed: %s', error);
				callback(new Error('Request to API failed: ' + error));
				done();
				return;
			}

			if (response.statusCode !== 200) {
				self.logger.error('Request to API failed: HTTP status code was %d for <%s>', response.statusCode || 'unknown', options.url);
				self.logger.data(new Error().stack);

				callback(new Error('Request to API failed: HTTP status code was ' + response.statusCode));
				done();
				return;
			}

			// parse response
			var data,
				info,
				next;

			try {
				data = JSON.parse(body);
				info = data && data[actionName];
				next = data && data['query-continue'] && data['query-continue'][params.list];
			}
			catch(e) {
				self.logger.error('Error parsing JSON response: %s',  body);
				self.logger.data(body);

				callback(new Error('Error parsing JSON response'));
				done();
				return;
			}

			//if (!callback) data.error = {info: 'foo'}; // debug

			if (data && !data.error) {
				if (next) {
					self.logger.debug("There's more data");
					self.logger.debug(next);
				}

				callback(null, info, next, data);
			}
			else if (data.error) {
				self.logger.error('Error returned by API: %s',  data.error.info);
				self.logger.data(data.error);

				callback(new Error('Error returned by API: ' + data.error.info));
			}
			done();
		});
	};

	// public interface
	api.prototype = {
		log: function() {
			this.logger.log.apply(this.logger, arguments);
		},

		info: function() {
			this.logger.info.apply(this.logger, arguments);
		},

		warn: function(msg, extra) {
			this.logger.warn.apply(this.logger, arguments);
		},

		error: function(msg, extra) {
			this.logger.error.apply(this.logger, arguments);
		},

		// adds request to the queue
		call: function(params, callback, method) {
			this.queue.push(function(done) {
				doRequest.apply(this, [params, callback, method, done]);
			}.bind(this));
		},

		// fetch an external resource
		fetchUrl: function(url, callback, encoding) {
			var self = this;
			encoding = encoding || 'utf-8';

			// add a request to the queue
			this.queue.push(function(done) {
				self.info("Fetching <%s> (as %s)...", url, encoding);

				request({
					url: url,
					encoding: (encoding === 'binary') ? null : encoding
				}, function (error, response, body) {
					if (!error && response.statusCode === 200) {
						self.info('<%s>: fetched %s kB', url, (body.length/1024).toFixed(2));
						callback(null, body);
					}
					else {
						if (!error) {
							error = new Error('HTTP status '+ response.statusCode);
						}

						self.error('Failed to fetch <' + url + '>');
						self.error(error.message);
						callback(error, body);
					}

					done();
				});
			});
		}
	};

	return api;
}());
<|endoftext|>


<commit_before>/*
 * vim: ts=4:sw=4:expandtab
 */
(function () {
    'use strict';
    window.Whisper = window.Whisper || {};

    var Message  = window.Whisper.Message = Backbone.Model.extend({
        database  : Whisper.Database,
        storeName : 'messages',
        initialize: function() {
            this.on('change:attachments', this.updateImageUrl);
            this.on('destroy', this.revokeImageUrl);
        },
        defaults  : function() {
            return {
                timestamp: new Date().getTime(),
                attachments: []
            };
        },
        validate: function(attributes, options) {
            var required = ['conversationId', 'received_at', 'sent_at'];
            var missing = _.filter(required, function(attr) { return !attributes[attr]; });
            if (missing.length) {
                console.log("Message missing attributes: " + missing);
            }
        },
        isEndSession: function() {
            var flag = textsecure.protobuf.DataMessage.Flags.END_SESSION;
            return !!(this.get('flags') & flag);
        },
        isGroupUpdate: function() {
            return !!(this.get('group_update'));
        },
        isIncoming: function() {
            return this.get('type') === 'incoming';
        },
        getDescription: function() {
            if (this.isGroupUpdate()) {
                var group_update = this.get('group_update');
                if (group_update.left) {
                    return group_update.left + ' left the group.';
                }

                var messages = ['Updated the group.'];
                if (group_update.name) {
                    messages.push("Title is now '" + group_update.name + "'.");
                }
                if (group_update.joined) {
                    messages.push(group_update.joined.join(', ') + ' joined the group.');
                }

                return messages.join(' ');
            }
            if (this.isEndSession()) {
                return 'Secure session ended.';
            }
            if (this.isIncoming() && this.hasKeyConflicts()) {
                return 'Received message with unknown identity key.';
            }
            if (this.isIncoming() && this.hasErrors()) {
                return 'Error handling incoming message.';
            }

            return this.get('body');
        },
        getNotificationText: function() {
            var description = this.getDescription();
            if (description) {
                return description;
            }
            if (this.get('attachments').length > 0) {
                return 'Media message';
            }

            return '';
        },
        updateImageUrl: function() {
            this.revokeImageUrl();
            var attachment = this.get('attachments')[0];
            if (attachment) {
                var blob = new Blob([attachment.data], {
                    type: attachment.contentType
                });
                this.imageUrl = URL.createObjectURL(blob);
            } else {
                this.imageUrl = null;
            }
        },
        revokeImageUrl: function() {
            if (this.imageUrl) {
                URL.revokeObjectURL(this.imageUrl);
                this.imageUrl = null;
            }
        },
        getImageUrl: function() {
            if (this.imageUrl === undefined) {
                this.updateImageUrl();
            }
            return this.imageUrl;
        },
        getContact: function() {
            var conversationId = this.get('source');
            if (!this.isIncoming()) {
                conversationId = textsecure.storage.user.getNumber();
            }
            var c = ConversationController.get(conversationId);
            if (!c) {
                c = ConversationController.create({id: conversationId});
                c.fetch();
            }
            return c;
        },
        isOutgoing: function() {
            return this.get('type') === 'outgoing';
        },
        hasErrors: function() {
            return _.size(this.get('errors')) > 0;
        },
        hasKeyConflicts: function() {
            return _.any(this.get('errors'), function(e) {
                return (e.name === 'IncomingIdentityKeyError' ||
                        e.name === 'OutgoingIdentityKeyError');
            });
        },
        hasKeyConflict: function(number) {
            return _.any(this.get('errors'), function(e) {
                return (e.name === 'IncomingIdentityKeyError' ||
                        e.name === 'OutgoingIdentityKeyError') &&
                        e.number === number;
            });
        },
        getKeyConflict: function(number) {
            return _.find(this.get('errors'), function(e) {
                return (e.name === 'IncomingIdentityKeyError' ||
                        e.name === 'OutgoingIdentityKeyError') &&
                        e.number === number;
            });
        },

        send: function(promise) {
            return promise.then(function() {
                this.save({sent: true});
            }.bind(this)).catch(function(errors) {
                this.save({sent: true});
                if (!(errors instanceof Array)) {
                    errors = [errors];
                }
                errors.forEach(function(e) {
                    console.log(e);
                    console.log(e.reason, e.stack);
                });
                this.save({
                    errors : errors.map(function(e) {
                        if (e.constructor === Error) {
                            return _.pick(e, 'name', 'message', 'code', 'number', 'reason');
                        }
                        return e;
                    })
                });
            }.bind(this));
        },

        resolveConflict: function(number) {
            var error = this.getKeyConflict(number);
            if (error) {
                var promise = new textsecure.ReplayableError(error).replay();
                if (this.isIncoming()) {
                    promise.then(function(dataMessage) {
                        this.handleDataMessage(dataMessage);
                        this.save('errors', []);
                    }.bind(this)).catch(function(e) {
                        //this.save('errors', [_.pick(e, ['name', 'message'])]);
                        var errors = this.get('errors').concat(
                            _.pick(e, ['name', 'message'])
                        );
                        this.save('errors', errors);
                    }.bind(this));
                } else {
                    promise.then(function() {
                        var errors = _.reject(this.get('errors'), function(e) {
                            return e.name === 'OutgoingIdentityKeyError' &&
                                   e.number === number;
                        });
                        this.save({sent: true, errors: errors});
                    }.bind(this));
                }
                return promise;
            }
        },
        handleDataMessage: function(dataMessage) {
            // This function can be called from the background script on an
            // incoming message or from the frontend after the user accepts an
            // identity key change.
            var message = this;
            var source = message.get('source');
            var type = message.get('type');
            var timestamp = message.get('sent_at');
            var conversationId = message.get('conversationId');
            if (dataMessage.group) {
                conversationId = dataMessage.group.id;
            }
            var conversation = ConversationController.create({id: conversationId});
            conversation.fetch().always(function() {
                var now = new Date().getTime();
                var attributes = { type: 'private' };
                if (dataMessage.group) {
                    var group_update = {};
                    attributes = {
                        type: 'group',
                        groupId: dataMessage.group.id,
                    };
                    if (dataMessage.group.type === textsecure.protobuf.GroupContext.Type.UPDATE) {
                        attributes = {
                            type       : 'group',
                            groupId    : dataMessage.group.id,
                            name       : dataMessage.group.name,
                            avatar     : dataMessage.group.avatar,
                            members    : dataMessage.group.members,
                        };
                        group_update = conversation.changedAttributes(_.pick(dataMessage.group, 'name', 'avatar')) || {};
                        var difference = _.difference(dataMessage.group.members, conversation.get('members'));
                        if (difference.length > 0) {
                            group_update.joined = difference;
                        }
                    }
                    else if (dataMessage.group.type === textsecure.protobuf.GroupContext.Type.QUIT) {
                        group_update = { left: source };
                        attributes.members = _.without(conversation.get('members'), source);
                    }

                    if (_.keys(group_update).length > 0) {
                        message.set({group_update: group_update});
                    }
                }
                if (type === 'outgoing') {
                    // lazy hack - check for receipts that arrived early.
                    if (dataMessage.group && dataMessage.group.id) {  // group sync
                        var members = conversation.get('members') || [];
                        var receipts = window.receipts.where({ timestamp: timestamp });
                        for (var i in receipts) {
                            if (members.indexOf(receipts[i].get('source')) > -1) {
                                window.receipts.remove(receipts[i]);
                                message.set({
                                    delivered: (message.get('delivered') || 0) + 1
                                });
                            }
                        }
                    } else {
                        var receipt = window.receipts.findWhere({
                            timestamp: timestamp,
                            source: conversationId
                        });
                        if (receipt) {
                            window.receipts.remove(receipt);
                            message.set({
                                delivered: (message.get('delivered') || 0) + 1
                            });
                        }
                    }
                }
                attributes.active_at = now;
                if (type === 'incoming') {
                    attributes.unreadCount = conversation.get('unreadCount') + 1;
                }
                conversation.set(attributes);

                message.set({
                    body           : dataMessage.body,
                    conversationId : conversation.id,
                    attachments    : dataMessage.attachments,
                    decrypted_at   : now,
                    flags          : dataMessage.flags,
                    errors         : []
                });

                var conversation_timestamp = conversation.get('timestamp');
                if (!conversation_timestamp || message.get('sent_at') > conversation_timestamp) {
                    conversation.set({
                        timestamp: message.get('sent_at'),
                        lastMessage: message.get('body')
                    });
                }
                else if (!conversation.get('lastMessage')) {
                    conversation.set({
                        lastMessage: message.get('body')
                    });
                }

                conversation.save().then(function() {
                    message.save().then(function() {
                        if (message.isIncoming()) {
                            notifyConversation(message);
                        } else {
                            conversation.trigger('newmessages');
                        }
                    });
                });
            });
        }

    });

    Whisper.MessageCollection = Backbone.Collection.extend({
        model      : Message,
        database   : Whisper.Database,
        storeName  : 'messages',
        comparator : 'received_at',
        initialize : function(models, options) {
            if (options) {
                this.conversation = options.conversation;
            }
        },
        destroyAll : function () {
            return Promise.all(this.models.map(function(m) {
                return new Promise(function(resolve, reject) {
                    m.destroy().then(resolve).fail(reject);
                });
            }));
        },

        fetchSentAt: function(timestamp) {
            return this.fetch({
                index: {
                    // 'receipt' index on sent_at
                    name: 'receipt',
                    only: timestamp
                }
            });
        },

        fetchConversation: function(conversationId) {
            var options = {remove: false};
            options.index = {
                // 'conversation' index on [conversationId, received_at]
                name  : 'conversation',
                lower : [conversationId],
                upper : [conversationId, Number.MAX_VALUE]
                // SELECT messages WHERE conversationId = this.id ORDER
                // received_at DESC
            };
            // TODO pagination/infinite scroll
            // limit: 10, offset: page*10,
            return this.fetch(options);
        },

        hasKeyConflicts: function() {
            return this.any(function(m) { return m.hasKeyConflicts(); });
        }
    });
})();
<commit_msg>Fix test failure<commit_after>/*
 * vim: ts=4:sw=4:expandtab
 */
(function () {
    'use strict';
    window.Whisper = window.Whisper || {};

    var Message  = window.Whisper.Message = Backbone.Model.extend({
        database  : Whisper.Database,
        storeName : 'messages',
        initialize: function() {
            this.on('change:attachments', this.updateImageUrl);
            this.on('destroy', this.revokeImageUrl);
        },
        defaults  : function() {
            return {
                timestamp: new Date().getTime(),
                attachments: []
            };
        },
        validate: function(attributes, options) {
            var required = ['conversationId', 'received_at', 'sent_at'];
            var missing = _.filter(required, function(attr) { return !attributes[attr]; });
            if (missing.length) {
                console.log("Message missing attributes: " + missing);
            }
        },
        isEndSession: function() {
            var flag = textsecure.protobuf.DataMessage.Flags.END_SESSION;
            return !!(this.get('flags') & flag);
        },
        isGroupUpdate: function() {
            return !!(this.get('group_update'));
        },
        isIncoming: function() {
            return this.get('type') === 'incoming';
        },
        getDescription: function() {
            if (this.isGroupUpdate()) {
                var group_update = this.get('group_update');
                if (group_update.left) {
                    return group_update.left + ' left the group.';
                }

                var messages = ['Updated the group.'];
                if (group_update.name) {
                    messages.push("Title is now '" + group_update.name + "'.");
                }
                if (group_update.joined) {
                    messages.push(group_update.joined.join(', ') + ' joined the group.');
                }

                return messages.join(' ');
            }
            if (this.isEndSession()) {
                return 'Secure session ended.';
            }
            if (this.isIncoming() && this.hasKeyConflicts()) {
                return 'Received message with unknown identity key.';
            }
            if (this.isIncoming() && this.hasErrors()) {
                return 'Error handling incoming message.';
            }

            return this.get('body');
        },
        getNotificationText: function() {
            var description = this.getDescription();
            if (description) {
                return description;
            }
            if (this.get('attachments').length > 0) {
                return 'Media message';
            }

            return '';
        },
        updateImageUrl: function() {
            this.revokeImageUrl();
            var attachment = this.get('attachments')[0];
            if (attachment) {
                var blob = new Blob([attachment.data], {
                    type: attachment.contentType
                });
                this.imageUrl = URL.createObjectURL(blob);
            } else {
                this.imageUrl = null;
            }
        },
        revokeImageUrl: function() {
            if (this.imageUrl) {
                URL.revokeObjectURL(this.imageUrl);
                this.imageUrl = null;
            }
        },
        getImageUrl: function() {
            if (this.imageUrl === undefined) {
                this.updateImageUrl();
            }
            return this.imageUrl;
        },
        getContact: function() {
            var conversationId = this.get('source');
            if (!this.isIncoming()) {
                conversationId = textsecure.storage.user.getNumber();
            }
            var c = ConversationController.get(conversationId);
            if (!c) {
                c = ConversationController.create({id: conversationId});
                c.fetch();
            }
            return c;
        },
        isOutgoing: function() {
            return this.get('type') === 'outgoing';
        },
        hasErrors: function() {
            return _.size(this.get('errors')) > 0;
        },
        hasKeyConflicts: function() {
            return _.any(this.get('errors'), function(e) {
                return (e.name === 'IncomingIdentityKeyError' ||
                        e.name === 'OutgoingIdentityKeyError');
            });
        },
        hasKeyConflict: function(number) {
            return _.any(this.get('errors'), function(e) {
                return (e.name === 'IncomingIdentityKeyError' ||
                        e.name === 'OutgoingIdentityKeyError') &&
                        e.number === number;
            });
        },
        getKeyConflict: function(number) {
            return _.find(this.get('errors'), function(e) {
                return (e.name === 'IncomingIdentityKeyError' ||
                        e.name === 'OutgoingIdentityKeyError') &&
                        e.number === number;
            });
        },

        send: function(promise) {
            return promise.then(function() {
                this.save({sent: true});
            }.bind(this)).catch(function(errors) {
                if (!(errors instanceof Array)) {
                    errors = [errors];
                }
                errors.forEach(function(e) {
                    console.log(e);
                    console.log(e.reason, e.stack);
                });
                this.save({
                    sent: true,
                    errors : errors.map(function(e) {
                        if (e.constructor === Error) {
                            return _.pick(e, 'name', 'message', 'code', 'number', 'reason');
                        }
                        return e;
                    })
                });
            }.bind(this));
        },

        resolveConflict: function(number) {
            var error = this.getKeyConflict(number);
            if (error) {
                var promise = new textsecure.ReplayableError(error).replay();
                if (this.isIncoming()) {
                    promise.then(function(dataMessage) {
                        this.handleDataMessage(dataMessage);
                        this.save('errors', []);
                    }.bind(this)).catch(function(e) {
                        //this.save('errors', [_.pick(e, ['name', 'message'])]);
                        var errors = this.get('errors').concat(
                            _.pick(e, ['name', 'message'])
                        );
                        this.save('errors', errors);
                    }.bind(this));
                } else {
                    promise.then(function() {
                        var errors = _.reject(this.get('errors'), function(e) {
                            return e.name === 'OutgoingIdentityKeyError' &&
                                   e.number === number;
                        });
                        this.save({sent: true, errors: errors});
                    }.bind(this));
                }
                return promise;
            }
        },
        handleDataMessage: function(dataMessage) {
            // This function can be called from the background script on an
            // incoming message or from the frontend after the user accepts an
            // identity key change.
            var message = this;
            var source = message.get('source');
            var type = message.get('type');
            var timestamp = message.get('sent_at');
            var conversationId = message.get('conversationId');
            if (dataMessage.group) {
                conversationId = dataMessage.group.id;
            }
            var conversation = ConversationController.create({id: conversationId});
            conversation.fetch().always(function() {
                var now = new Date().getTime();
                var attributes = { type: 'private' };
                if (dataMessage.group) {
                    var group_update = {};
                    attributes = {
                        type: 'group',
                        groupId: dataMessage.group.id,
                    };
                    if (dataMessage.group.type === textsecure.protobuf.GroupContext.Type.UPDATE) {
                        attributes = {
                            type       : 'group',
                            groupId    : dataMessage.group.id,
                            name       : dataMessage.group.name,
                            avatar     : dataMessage.group.avatar,
                            members    : dataMessage.group.members,
                        };
                        group_update = conversation.changedAttributes(_.pick(dataMessage.group, 'name', 'avatar')) || {};
                        var difference = _.difference(dataMessage.group.members, conversation.get('members'));
                        if (difference.length > 0) {
                            group_update.joined = difference;
                        }
                    }
                    else if (dataMessage.group.type === textsecure.protobuf.GroupContext.Type.QUIT) {
                        group_update = { left: source };
                        attributes.members = _.without(conversation.get('members'), source);
                    }

                    if (_.keys(group_update).length > 0) {
                        message.set({group_update: group_update});
                    }
                }
                if (type === 'outgoing') {
                    // lazy hack - check for receipts that arrived early.
                    if (dataMessage.group && dataMessage.group.id) {  // group sync
                        var members = conversation.get('members') || [];
                        var receipts = window.receipts.where({ timestamp: timestamp });
                        for (var i in receipts) {
                            if (members.indexOf(receipts[i].get('source')) > -1) {
                                window.receipts.remove(receipts[i]);
                                message.set({
                                    delivered: (message.get('delivered') || 0) + 1
                                });
                            }
                        }
                    } else {
                        var receipt = window.receipts.findWhere({
                            timestamp: timestamp,
                            source: conversationId
                        });
                        if (receipt) {
                            window.receipts.remove(receipt);
                            message.set({
                                delivered: (message.get('delivered') || 0) + 1
                            });
                        }
                    }
                }
                attributes.active_at = now;
                if (type === 'incoming') {
                    attributes.unreadCount = conversation.get('unreadCount') + 1;
                }
                conversation.set(attributes);

                message.set({
                    body           : dataMessage.body,
                    conversationId : conversation.id,
                    attachments    : dataMessage.attachments,
                    decrypted_at   : now,
                    flags          : dataMessage.flags,
                    errors         : []
                });

                var conversation_timestamp = conversation.get('timestamp');
                if (!conversation_timestamp || message.get('sent_at') > conversation_timestamp) {
                    conversation.set({
                        timestamp: message.get('sent_at'),
                        lastMessage: message.get('body')
                    });
                }
                else if (!conversation.get('lastMessage')) {
                    conversation.set({
                        lastMessage: message.get('body')
                    });
                }

                conversation.save().then(function() {
                    message.save().then(function() {
                        if (message.isIncoming()) {
                            notifyConversation(message);
                        } else {
                            conversation.trigger('newmessages');
                        }
                    });
                });
            });
        }

    });

    Whisper.MessageCollection = Backbone.Collection.extend({
        model      : Message,
        database   : Whisper.Database,
        storeName  : 'messages',
        comparator : 'received_at',
        initialize : function(models, options) {
            if (options) {
                this.conversation = options.conversation;
            }
        },
        destroyAll : function () {
            return Promise.all(this.models.map(function(m) {
                return new Promise(function(resolve, reject) {
                    m.destroy().then(resolve).fail(reject);
                });
            }));
        },

        fetchSentAt: function(timestamp) {
            return this.fetch({
                index: {
                    // 'receipt' index on sent_at
                    name: 'receipt',
                    only: timestamp
                }
            });
        },

        fetchConversation: function(conversationId) {
            var options = {remove: false};
            options.index = {
                // 'conversation' index on [conversationId, received_at]
                name  : 'conversation',
                lower : [conversationId],
                upper : [conversationId, Number.MAX_VALUE]
                // SELECT messages WHERE conversationId = this.id ORDER
                // received_at DESC
            };
            // TODO pagination/infinite scroll
            // limit: 10, offset: page*10,
            return this.fetch(options);
        },

        hasKeyConflicts: function() {
            return this.any(function(m) { return m.hasKeyConflicts(); });
        }
    });
})();
<|endoftext|>


<commit_before>import React from "react";
import { Match } from "react-router";

class Todos extends React.Component {
  state = {
    todos: [
      { id: 0, text: "Hello there!", completed: true },
      { id: 1, text: "How are you?", completed: false },
      { id: 2, text: "...well.", completed: false }
    ]
  };

  nextTodoId = this.state.todos.length;

  addTodo = text => {
    const newTodo = { id: this.nextTodoId++, text, completed: false };
    this.setState({ todos: [ ...this.state.todos, newTodo ] });
  };

  updateTodo = (id, update) => {
    // using the function form of "setState" to prevent "toggle all" from getting stale state
    this.setState(state => ({
      todos: state.todos.map(
        todo => todo.id === id ? { ...todo, ...update } : todo
      )
    }));
  };

  deleteTodo = id => {
    this.setState({ todos: this.state.todos.filter(todo => todo.id !== id) });
  };

  clearCompleted = () => {
    this.setState({ todos: this.state.todos.filter(todo => !todo.completed) });
  };

  render() {
    const { todos } = this.state;
    const renderProps = {
      ...this.state,
      addTodo: this.addTodo,
      updateTodo: this.updateTodo,
      deleteTodo: this.deleteTodo,
      clearCompleted: this.clearCompleted
    };

    return (
      <Match
        pattern="/:filter?"
        render={
          ({ params: { filter } }) =>
            filter === "active"
              ? this.props.children({
                ...renderProps,
                todos: todos.filter(todo => !todo.completed)
              })
              : filter === "completed"
                ? this.props.children({
                  ...renderProps,
                  todos: todos.filter(todo => todo.completed)
                })
                : this.props.children(renderProps)
        }
      />
    );
  }
}

export default Todos;
<commit_msg>making URL filtering more succinct (though less readable...)<commit_after>import React from "react";
import { Match } from "react-router";

class Todos extends React.Component {
  state = {
    todos: [
      { id: 0, text: "Hello there!", completed: true },
      { id: 1, text: "How are you?", completed: false },
      { id: 2, text: "...well.", completed: false }
    ]
  };

  nextTodoId = this.state.todos.length;

  addTodo = text => {
    const newTodo = { id: this.nextTodoId++, text, completed: false };
    this.setState(state => ({ todos: [ ...state.todos, newTodo ] }));
  };

  updateTodo = (id, update) => {
    this.setState(state => ({
      todos: state.todos.map(
        todo => todo.id === id ? { ...todo, ...update } : todo
      )
    }));
  };

  deleteTodo = id => {
    this.setState(state => ({
      todos: state.todos.filter(todo => todo.id !== id)
    }));
  };

  clearCompleted = () => {
    this.setState(state => ({
      todos: state.todos.filter(todo => !todo.completed)
    }));
  };

  render() {
    const { todos } = this.state;

    return (
      <Match
        pattern="/:filter?"
        render={
          ({ params: { filter } }) =>
            this.props.children({
              todos: todos.filter(
                filter === "active"
                  ? todo => !todo.completed
                  : filter === "completed"
                    ? todo => todo.completed
                    : todo => todo
              ),
              addTodo: this.addTodo,
              updateTodo: this.updateTodo,
              deleteTodo: this.deleteTodo,
              clearCompleted: this.clearCompleted
            })
        }
      />
    );
  }
}

export default Todos;
<|endoftext|>