<commit_before>import _ from 'lodash'; // eslint-disable-line
import React, { PropTypes } from 'react'; // eslint-disable-line
import Match from 'react-router/Match'; // eslint-disable-line
import {Row, Col} from 'react-bootstrap'; // eslint-disable-line

import Pane from '@folio/stripes-components/lib/Pane'; // eslint-disable-line
import Paneset from '@folio/stripes-components/lib/Paneset'; // eslint-disable-line
import PaneMenu from '@folio/stripes-components/lib/PaneMenu'; // eslint-disable-line
import Button from '@folio/stripes-components/lib/Button'; // eslint-disable-line
import Icon from '@folio/stripes-components/lib/Icon'; // eslint-disable-line
import Checkbox from '@folio/stripes-components/lib/Checkbox'; // eslint-disable-line
import TextField from '@folio/stripes-components/lib/TextField'; // eslint-disable-line
import MultiColumnList from '@folio/stripes-components/lib/MultiColumnList'; // eslint-disable-line
import KeyValue from '@folio/stripes-components/lib/KeyValue'; // eslint-disable-line
import FilterPaneSearch from '@folio/stripes-components/lib/FilterPaneSearch'; // eslint-disable-line
import FilterControlGroup from '@folio/stripes-components/lib/FilterControlGroup'; // eslint-disable-line
import Select from '@folio/stripes-components/lib/Select'; // eslint-disable-line
import Layer from '@folio/stripes-components/lib/Layer'; // eslint-disable-line

import UserForm from './UserForm';
import ViewUser from './ViewUser';

class Users extends React.Component {
  static contextTypes = {
    router: PropTypes.object.isRequired,
  };

  static propTypes = {
    data: PropTypes.object.isRequired,
    pathname: PropTypes.string.isRequired,
    location: PropTypes.shape({
      pathname: PropTypes.string.isRequired,
      query: PropTypes.object,
    }).isRequired,
    mutator: PropTypes.object,
  };

  static manifest = Object.freeze({
    users: {
      type: 'okapi',
      records: 'users',
      path: (queryParams, _pathParams) => {
        // console.log('Users manifest "users" path function, queryParams = ', queryParams);
        const { query, filterActive, filterInactive, sort } = queryParams || {};

        let cql;
        if (query) {
          cql = `username="${query}*" or personal.first_name="${query}*" or personal.last_name="${query}*"`;
        }

        let filterCql;
        if (filterActive && !filterInactive) {
          filterCql = 'active=true';
        } else if (filterInactive && !filterActive) {
          filterCql = 'active=false';
        } else if (!filterActive && !filterInactive) {
          // Technically, we should force this configuration to find
          // no records; but it probably makes more sense to do
          // nothing, and allow both active AND inactive records.
        }

        if (filterCql) {
          if (cql) {
            cql = `(${cql}) and ${filterCql}`;
          } else {
            cql = filterCql;
          }
        }

        if (sort) {
          const sortMap = {
            Active: 'active',
            Name: 'personal.last_name personal.first_name',
            Username: 'username',
            Email: 'personal.email',
          };
          const sortIndex = sortMap[sort];
          if (sortIndex) {
            if (cql === undefined) cql = 'username=*';
            cql += ` sortby ${sortIndex}`;
          }
        }

        let path = 'users';
        if (cql) path += `?query=title=${encodeURIComponent(cql)}`;

        console.log(`query=${query} active=${filterActive} inactive=${filterInactive} sort=${sort} -> ${path}`);
        return path;
      },
      staticFallback: { path: 'users' },
    },
  });

  constructor(props) {
    super(props);

    const query = props.location.query || {};
    this.state = {
      filter: {
        active: query.filterActive || false,
        inactive: query.filterInactive || false,
      },
      selectedItem: {},
      searchTerm: query.query || '',
      sortOrder: query.sort || '',
      addUserMode: false,
    };

    this.onClickAddNewUser = this.onClickAddNewUser.bind(this);
    this.onClickCloseNewUser = this.onClickCloseNewUser.bind(this);
    this.onChangeFilter = this.onChangeFilter.bind(this);
    this.onChangeSearch = this.onChangeSearch.bind(this);
    this.onClearSearch = this.onClearSearch.bind(this);
    this.onSort = this.onSort.bind(this);
    this.onSelectRow = this.onSelectRow.bind(this);
  }

  // search Handlers...
  onChangeFilter(e) {
    const filter = this.state.filter;
    filter[e.target.id] = !filter[e.target.id];
    console.log('onChangeFilter setting state', filter);
    this.setState({ filter });
    this.updateSearch(this.state.searchTerm, this.state.sortOrder, filter);
  }

  onChangeSearch(e) {
    const query = e.target.value;
    console.log(`User searched for '${query}' at '${this.props.location.pathname}'`);

    this.setState({ searchTerm: query });
    this.updateSearch(query, this.state.sortOrder, this.state.filter);
  }

  onClearSearch() {
    console.log('User cleared search');
    this.setState({ searchTerm: '' });
    this.context.router.transitionTo(this.props.location.pathname);
  }

  onSort(e, meta) {
    const sortOrder = meta.name;
    console.log('User sorted by', sortOrder);
    this.setState({ sortOrder });
    this.updateSearch(this.state.searchTerm, sortOrder, this.state.filter);
  }

  onSelectRow(e, meta) {
    const userId = meta.id;
    console.log('User clicked', userId, 'location = ', this.props.location);
    this.setState({ selectedItem: meta });
    this.context.router.transitionTo(`/users/view/${userId}${this.props.location.search}`);
  }

  // end search Handlers

  // AddUser Handlers
  onClickAddNewUser(e) {
    if (e) e.preventDefault();
    this.setState({
      addUserMode: true,
    });
  }

  onClickCloseNewUser(e) {
    if (e) e.preventDefault();
    this.setState({
      addUserMode: false,
    });
  }
  // end AddUser Handlers

  // We need to explicitly pass changed values into this function,
  // as state-change only happens after event is handled.
  updateSearch(query, sortOrder, filter) {
    console.log(`updateSearch('${query}', '${sortOrder}',`, filter, ')');
    let transitionLoc = this.props.location.pathname;
    const params = {};
    if (query) params.query = query;
    if (sortOrder) params.sort = sortOrder;
    if (filter.active) params.filterActive = true;
    if (filter.inactive) params.filterInactive = true;
    const keys = Object.keys(params);
    if (keys.length) {
      // eslint-disable-next-line prefer-template
      transitionLoc += '?' + keys.map(key => `${key}=${encodeURIComponent(params[key])}`).join('&');
    }
    this.context.router.transitionTo(transitionLoc);
  }

  create(data) {
    this.props.mutator.users.POST(data);
  }

  render() {
    const { data, pathname } = this.props;
    const users = data.users || [];

    const resultMenu = <PaneMenu><button><Icon icon="bookmark" /></button></PaneMenu>;
    const fineHistory = [{ 'Due Date': '11/12/2014', 'Amount': '34.23', 'Status': 'Unpaid' }]; // eslint-disable-line

    const resultsFormatter = {
      Active: user => user.active,
      Name: user => `${_.get(user, ['personal', 'last_name'], '')}, ${_.get(user, ['personal', 'first_name'], '')}`,
      Username: user => user.username,
      Email: user => _.get(user, ['personal', 'email']),
    };

    /* searchHeader is a 'custom pane header'*/
    const searchHeader = <FilterPaneSearch id="SearchField" onChange={this.onChangeSearch} onClear={this.onClearSearch} value={this.state.searchTerm} />;

    return (
      <Paneset>
        {/* Filter Pane */}
        <Pane defaultWidth="16%" header={searchHeader}>
          <FilterControlGroup label="Filters">
            <Checkbox
              id="active"
              label="Active"
              checked={this.state.filter.active}
              onChange={this.onChangeFilter}
              marginBottom0
              hover
              fullWidth
              checkedIcon={<Icon icon="eye" />}
            />
            <Checkbox
              id="inactive"
              label="Inactive"
              checked={this.state.filter.inactive}
              onChange={this.onChangeFilter}
              marginBottom0
              hover
              fullWidth
              checkedIcon={<Icon icon="eye" />}
            />
          </FilterControlGroup>
          <FilterControlGroup label="Actions">
            <Button fullWidth onClick={this.onClickAddNewUser}>Add User</Button>
          </FilterControlGroup>
        </Pane>

        {/* Results Pane */}
        <Pane
          defaultWidth="40%"
          paneTitle={

              Results

                {users.length} Result{users.length === 1 ? '' : 's'} Found

          }
          lastMenu={resultMenu}
        >
          <MultiColumnList
            contentData={users}
            selectedRow={this.state.selectedItem}
            rowMetadata={['id']}
            formatter={resultsFormatter}
            onRowClick={this.onSelectRow}
            onHeaderClick={this.onSort}
            visibleColumns={['Active', 'Name', 'Username', 'Email']}
            fullWidth
            sortOrder={this.state.sortOrder}
          />
        </Pane>

        {/* Details Pane */}
        <Match pattern={`${pathname}/view/:userid`} render={props => <ViewUser placeholder={'placeholder'} {...props} />} />
        <Layer isOpen={this.state.addUserMode} label="Add New User Dialog">
          <UserForm
            onSubmit={(record) => { this.create(record); }}
            onCancel={this.onClickCloseNewUser}
          />
        </Layer>
      </Paneset>
    );
  }
}

export default Users;
<commit_msg>Reorder some assignments to match what Items.js does.<commit_after>import _ from 'lodash'; // eslint-disable-line
import React, { PropTypes } from 'react'; // eslint-disable-line
import Match from 'react-router/Match'; // eslint-disable-line
import {Row, Col} from 'react-bootstrap'; // eslint-disable-line

import Pane from '@folio/stripes-components/lib/Pane'; // eslint-disable-line
import Paneset from '@folio/stripes-components/lib/Paneset'; // eslint-disable-line
import PaneMenu from '@folio/stripes-components/lib/PaneMenu'; // eslint-disable-line
import Button from '@folio/stripes-components/lib/Button'; // eslint-disable-line
import Icon from '@folio/stripes-components/lib/Icon'; // eslint-disable-line
import Checkbox from '@folio/stripes-components/lib/Checkbox'; // eslint-disable-line
import TextField from '@folio/stripes-components/lib/TextField'; // eslint-disable-line
import MultiColumnList from '@folio/stripes-components/lib/MultiColumnList'; // eslint-disable-line
import KeyValue from '@folio/stripes-components/lib/KeyValue'; // eslint-disable-line
import FilterPaneSearch from '@folio/stripes-components/lib/FilterPaneSearch'; // eslint-disable-line
import FilterControlGroup from '@folio/stripes-components/lib/FilterControlGroup'; // eslint-disable-line
import Select from '@folio/stripes-components/lib/Select'; // eslint-disable-line
import Layer from '@folio/stripes-components/lib/Layer'; // eslint-disable-line

import UserForm from './UserForm';
import ViewUser from './ViewUser';

class Users extends React.Component {
  static contextTypes = {
    router: PropTypes.object.isRequired,
  };

  static propTypes = {
    data: PropTypes.object.isRequired,
    pathname: PropTypes.string.isRequired,
    location: PropTypes.shape({
      pathname: PropTypes.string.isRequired,
      query: PropTypes.object,
    }).isRequired,
    mutator: PropTypes.object,
  };

  static manifest = Object.freeze({
    users: {
      type: 'okapi',
      records: 'users',
      path: (queryParams, _pathParams) => {
        // console.log('Users manifest "users" path function, queryParams = ', queryParams);
        const { query, filterActive, filterInactive, sort } = queryParams || {};

        let cql;
        if (query) {
          cql = `username="${query}*" or personal.first_name="${query}*" or personal.last_name="${query}*"`;
        }

        let filterCql;
        if (filterActive && !filterInactive) {
          filterCql = 'active=true';
        } else if (filterInactive && !filterActive) {
          filterCql = 'active=false';
        } else if (!filterActive && !filterInactive) {
          // Technically, we should force this configuration to find
          // no records; but it probably makes more sense to do
          // nothing, and allow both active AND inactive records.
        }

        if (filterCql) {
          if (cql) {
            cql = `(${cql}) and ${filterCql}`;
          } else {
            cql = filterCql;
          }
        }

        if (sort) {
          const sortMap = {
            Active: 'active',
            Name: 'personal.last_name personal.first_name',
            Username: 'username',
            Email: 'personal.email',
          };
          const sortIndex = sortMap[sort];
          if (sortIndex) {
            if (cql === undefined) cql = 'username=*';
            cql += ` sortby ${sortIndex}`;
          }
        }

        let path = 'users';
        if (cql) path += `?query=title=${encodeURIComponent(cql)}`;

        console.log(`query=${query} active=${filterActive} inactive=${filterInactive} sort=${sort} -> ${path}`);
        return path;
      },
      staticFallback: { path: 'users' },
    },
  });

  constructor(props) {
    super(props);

    const query = props.location.query || {};
    this.state = {
      filter: {
        active: query.filterActive || false,
        inactive: query.filterInactive || false,
      },
      selectedItem: {},
      searchTerm: query.query || '',
      sortOrder: query.sort || '',
      addUserMode: false,
    };

    this.onClickAddNewUser = this.onClickAddNewUser.bind(this);
    this.onClickCloseNewUser = this.onClickCloseNewUser.bind(this);
    this.onChangeFilter = this.onChangeFilter.bind(this);
    this.onChangeSearch = this.onChangeSearch.bind(this);
    this.onClearSearch = this.onClearSearch.bind(this);
    this.onSort = this.onSort.bind(this);
    this.onSelectRow = this.onSelectRow.bind(this);
  }

  // search Handlers...
  onChangeFilter(e) {
    const filter = this.state.filter;
    filter[e.target.id] = !filter[e.target.id];
    console.log('onChangeFilter setting state', filter);
    this.setState({ filter });
    this.updateSearch(this.state.searchTerm, this.state.sortOrder, filter);
  }

  onChangeSearch(e) {
    const query = e.target.value;
    console.log(`User searched for '${query}' at '${this.props.location.pathname}'`);

    this.setState({ searchTerm: query });
    this.updateSearch(query, this.state.sortOrder, this.state.filter);
  }

  onClearSearch() {
    console.log('User cleared search');
    this.setState({ searchTerm: '' });
    this.context.router.transitionTo(this.props.location.pathname);
  }

  onSort(e, meta) {
    const sortOrder = meta.name;
    console.log('User sorted by', sortOrder);
    this.setState({ sortOrder });
    this.updateSearch(this.state.searchTerm, sortOrder, this.state.filter);
  }

  onSelectRow(e, meta) {
    const userId = meta.id;
    console.log('User clicked', userId, 'location = ', this.props.location);
    this.setState({ selectedItem: meta });
    this.context.router.transitionTo(`/users/view/${userId}${this.props.location.search}`);
  }

  // end search Handlers

  // AddUser Handlers
  onClickAddNewUser(e) {
    if (e) e.preventDefault();
    this.setState({
      addUserMode: true,
    });
  }

  onClickCloseNewUser(e) {
    if (e) e.preventDefault();
    this.setState({
      addUserMode: false,
    });
  }
  // end AddUser Handlers

  // We need to explicitly pass changed values into this function,
  // as state-change only happens after event is handled.
  updateSearch(query, sortOrder, filter) {
    console.log(`updateSearch('${query}', '${sortOrder}',`, filter, ')');
    let transitionLoc = this.props.location.pathname;
    const params = {};
    if (query) params.query = query;
    if (sortOrder) params.sort = sortOrder;
    if (filter.active) params.filterActive = true;
    if (filter.inactive) params.filterInactive = true;
    const keys = Object.keys(params);
    if (keys.length) {
      // eslint-disable-next-line prefer-template
      transitionLoc += '?' + keys.map(key => `${key}=${encodeURIComponent(params[key])}`).join('&');
    }
    this.context.router.transitionTo(transitionLoc);
  }

  create(data) {
    this.props.mutator.users.POST(data);
  }

  render() {
    const { data, pathname } = this.props;
    const users = data.users || [];

    /* searchHeader is a 'custom pane header'*/
    const searchHeader = <FilterPaneSearch id="SearchField" onChange={this.onChangeSearch} onClear={this.onClearSearch} value={this.state.searchTerm} />;
    const resultMenu = <PaneMenu><button><Icon icon="bookmark" /></button></PaneMenu>;
    const fineHistory = [{ 'Due Date': '11/12/2014', 'Amount': '34.23', 'Status': 'Unpaid' }]; // eslint-disable-line

    const resultsFormatter = {
      Active: user => user.active,
      Name: user => `${_.get(user, ['personal', 'last_name'], '')}, ${_.get(user, ['personal', 'first_name'], '')}`,
      Username: user => user.username,
      Email: user => _.get(user, ['personal', 'email']),
    };

    return (
      <Paneset>
        {/* Filter Pane */}
        <Pane defaultWidth="16%" header={searchHeader}>
          <FilterControlGroup label="Filters">
            <Checkbox
              id="active"
              label="Active"
              checked={this.state.filter.active}
              onChange={this.onChangeFilter}
              marginBottom0
              hover
              fullWidth
              checkedIcon={<Icon icon="eye" />}
            />
            <Checkbox
              id="inactive"
              label="Inactive"
              checked={this.state.filter.inactive}
              onChange={this.onChangeFilter}
              marginBottom0
              hover
              fullWidth
              checkedIcon={<Icon icon="eye" />}
            />
          </FilterControlGroup>
          <FilterControlGroup label="Actions">
            <Button fullWidth onClick={this.onClickAddNewUser}>Add User</Button>
          </FilterControlGroup>
        </Pane>
        {/* Results Pane */}
        <Pane
          defaultWidth="40%"
          paneTitle={

              Results

                {users.length} Result{users.length === 1 ? '' : 's'} Found

          }
          lastMenu={resultMenu}
        >
          <MultiColumnList
            contentData={users}
            selectedRow={this.state.selectedItem}
            rowMetadata={['id']}
            formatter={resultsFormatter}
            onRowClick={this.onSelectRow}
            onHeaderClick={this.onSort}
            visibleColumns={['Active', 'Name', 'Username', 'Email']}
            fullWidth
            sortOrder={this.state.sortOrder}
          />
        </Pane>

        {/* Details Pane */}
        <Match pattern={`${pathname}/view/:userid`} render={props => <ViewUser placeholder={'placeholder'} {...props} />} />
        <Layer isOpen={this.state.addUserMode} label="Add New User Dialog">
          <UserForm
            onSubmit={(record) => { this.create(record); }}
            onCancel={this.onClickCloseNewUser}
          />
        </Layer>
      </Paneset>
    );
  }
}

export default Users;
<|endoftext|>


<commit_before><commit_msg>Audio stops even if no notifications<commit_after><|endoftext|>


<commit_before><commit_msg>Refactor property named 'export' to be 'info' so as to not use a reserved word. Refactor property named 'this' to be 'self' so as to not use a reserved word. makeAccessor function: - Provide this accessor factory the ability to not merge the accessor's info object into the accessor, through provision of option key dontMergeInfo = true; otherwise the info object's properties are by default merged into the accessor function. - Fix bug in setter and getter detection<commit_after><|endoftext|>


<commit_before>var fs = require('fs');
var _ = require('lodash');
var crypto = require('crypto');
var fs_extra = require('fs-extra');

const hash_sampling_percent = 100; // what percentage of files to sample when hashing (100 = no sampling)
const dry_run = false;
const blocksize = 512;

function adddir( path, progress_callback, progress ) {
	//console.log("adddir( " + path + " )" );
	if ( progress === undefined ) progress = [];
	var files = fs.readdirSync( path );
	var my_progress = {
		current: 0,
		count: files.length
	}
	progress.push( my_progress );

	files.forEach( (f) => {
		my_progress.current += 1;
		progress_callback( progress );
		var file = path + '/' + f;
		var stats = fs.lstatSync( file );
		if ( stats.isFile() ) {
			addfile( file );
		}
		if ( stats.isDirectory() ) {
			adddir( file, progress_callback, progress );
		}
	})

	progress.pop();
}

var by_size = {}
function addfile( file ) {
	var stats = fs.lstatSync( file );
	var id = "size_" + stats.size;
	if ( by_size[id] === undefined ) by_size[id] = [];
	by_size[id].push( file );
}

function calc_hash( file ) {
	var shasum = crypto.createHash( 'sha256' );
	var stats = fs.lstatSync( file );
	if ( hash_sampling_percent == 100 || stats.size < 1024*1024 ) {
		shasum.update( fs.readFileSync( file ) );
	} else {
		var buffer = new Buffer( blocksize );
		var block_count = stats.size / blocksize;
		var loop_count = block_count * (hash_sampling_percent / 100);
		var block_space = block_count / loop_count;
		//console.log("file size", stats.size, "block_count", block_count, "loop_count", loop_count, "block_space", block_space);
		var fd = fs.openSync( file, 'r' );
		for (var i=0; i<loop_count; i+=1) {
			var start = ( block_space * i ) * blocksize;
			fs.readSync( fd, buffer, 0, blocksize, start );
			shasum.update( buffer );
		}
		fs.closeSync( fd );
	}
	return shasum.digest( 'hex' );
}

var by_hash = {}
function addfile_by_hash( file, progress_callback ) {
	var hash = calc_hash( file );

	if ( by_hash[hash] === undefined ) by_hash[hash] = [];
	by_hash[hash].push( file );

	progress_callback( _.keys( by_hash ).length );
}

function remove_files( base_path, destination, files ) {
	files.forEach( (file) => {
		var dest_file = file.replace( base_path, destination + '/' );
		var dest_dir = dest_file.split('/').slice(0, -1).join('/');
		if ( dry_run ) {
			console.log("dry_run", file, " => ", dest_file);
		} else {
			fs_extra.mkdirpSync( dest_dir );
			fs.renameSync( file, dest_file );
		}
	});
	console.log('moved', files.length, 'files to "' + dest_dir + '" including directory structure')
}
// ---

console.log("welcome to duper.js: find and delete duplicate (by content) files\n");
console.log("\
usage:\n\tduper.js <directory> [<substring>:<preference>]*\n\n\
with\n\
	<directory> \tthe folder to scan for duplicates\n\
	<substring> \tstring to use for matching filename (incl. path)\n\
	<preference> \t[keep|delete] the action to preferrably take if filename (incl. path) matches <substring>\n\
how it works:\n\n\
	see README.md\n\
");

// parse cmdline args

var dir = process.argv[2];
if ( dir === undefined ) dir = '.';

var prefs =
_.map(
	_.takeRightWhile( process.argv, (a) => {
		return a.indexOf(':') >= 0
	}), (p) => {
		return {
			substring: p.split( ':' )[0],
			preference: p.split( ':' )[1]
		}
	}
);

// scan filesystem recursively, clustering files by size into global by_size

console.log( '\nscanning filesystem...' );
var progress_count = 0;
adddir( dir, (progress) => {
	progress_count += 1;
	if ( progress_count % 50 == 0 ) {
		var pstr = _.map( progress, (p) => {
			return "" + p.current + '/' + p.count;
		}).join( ' ' );
		process.stdout.write( 'scanning progress: ' + pstr + '                                                                                \r');
	}
});

// filter clusters having more than 1 file

dupes_by_size = _.filter( by_size, (entry) => {
	return entry.length > 1;
});
console.log('found', _.keys( dupes_by_size ).length, 'clusters by size match' );

// hash the duplicate files, clustering by file hash

console.log( '\ncalculating file hashes of file in equally-sized clusters... (using ' + hash_sampling_percent + '% content sampling)' );
var flat_dupes = _.flattenDeep( dupes_by_size );
flat_dupes.forEach( (file) => {
	addfile_by_hash( file, (count) => {
		process.stdout.write( 'hashing progress: ' + count + ' of ' + flat_dupes.length + '\r');
	});
});
process.stdout.write('\n\n');

// filter clusters having more than 1 entry

//console.log("by_hash", by_hash);

var dupes_by_hash = _.filter( by_hash, (entry) => {
	return entry.length > 1;
});

console.log('found', _.keys( dupes_by_hash ).length, 'clusters by hash match' );

console.log("dupes_by_hash", dupes_by_hash);

// select all except one file per cluster to delete

var deletion_list = _.map( dupes_by_hash, ( cluster ) => {
	//console.log('\n------ checking cluster: ', cluster );

	// map files in each cluster to actions by matching preference substring to filename
	var actions = {
		'delete': [],
		'keep': []
	};

	_.forEach( cluster, (file) => {
		// find first matching preference
		var matching_pref = _.filter( prefs, (pref) => {
			return file.indexOf( pref.substring ) >= 0;
		})[0];
		if ( matching_pref ) {
			actions[matching_pref.preference].push( file );
		}
	})

	// add more items ot 'delete' action until all except one are added
	var quantity_to_add = _.keys(cluster).length - actions['delete'].length - 1
	if ( quantity_to_add > 0 ) {
		// determine array of files neither in 'delete' nor 'keep' (those can be added to 'delete')
		var unmatched = _.filter( cluster, (file) => {
			return (
				actions['delete'].indexOf( file ) == -1 &&
				actions['keep'].indexOf( file ) == -1
			);
		});
		var additional_delete = unmatched.slice( 0, quantity_to_add)
		actions['delete'] = actions['delete'].concat( additional_delete );
		// add the remainder of the unmatched files to 'keep'
		actions['keep'] = actions['keep'].concat( unmatched.slice( quantity_to_add ) );
		// since we made a decision (without using a rule) about which file to delete,
		// we try to achieve some consistency regarding this selection in the future
		// by adding preferences generated from differences between the names of
		// the selected (for deletion) and the non-selected files
		var keep_tokens = _.reduce( actions['keep'], (result, file) => {
			return result.concat( file.split(/[^\w]/) );
		}, []);
		var selected_file_tokens = _.reduce( additional_delete, (result, file) => {
			return result.concat( file.split(/[^\w]/) );
		}, []);
		var delete_tokens = _.difference( selected_file_tokens, keep_tokens );
		// add the identifying tokens as substrings for delete preferences
		delete_tokens.forEach( (token) => {
			if (
				_.map( prefs, ( pref ) => {
					return pref.substring;
				}).indexOf( token ) == -1
			) {
				prefs.push({
					substring: token,
					preference: 'delete'
				});
			}
		});
	}
	// return files mapped to 'delete' action (but not too many)
	return actions['delete'].slice( 0, _.keys(cluster).length - 1 );
});
deletion_list = _.flatten( deletion_list );

console.log("final prefs: ", _.reduce( prefs, (a, p) => { return a + " " + p.substring + ':' + p.preference; }, ""));

//console.log( "will delete the following files:\n" + deletion_list.join('\n') );
console.log("deletion_list", deletion_list);
console.log("(", deletion_list.length, "files )");

var dest_dir = dir.split('/').slice(0, -1).join('/') + '/removed_by_duper(' + dir.split('/').slice(-1)[0] + ')';
remove_files( dir, dest_dir, deletion_list );
console.log("", deletion_list.length, "files have been moved to", dest_dir);<commit_msg>remove trailing slash from dir to fix dest_dir<commit_after>var fs = require('fs');
var _ = require('lodash');
var crypto = require('crypto');
var fs_extra = require('fs-extra');

const hash_sampling_percent = 0.1; // what percentage of files to sample when hashing (100 = no sampling)
const dry_run = false;
const blocksize = 1024;

function adddir( path, progress_callback, progress ) {
	//console.log("adddir( " + path + " )" );
	if ( progress === undefined ) progress = [];
	var files = fs.readdirSync( path );
	var my_progress = {
		current: 0,
		count: files.length
	}
	progress.push( my_progress );

	files.forEach( (f) => {
		my_progress.current += 1;
		progress_callback( progress );
		var file = path + '/' + f;
		var stats = fs.lstatSync( file );
		if ( stats.isFile() ) {
			addfile( file );
		}
		if ( stats.isDirectory() ) {
			adddir( file, progress_callback, progress );
		}
	})

	progress.pop();
}

var by_size = {}
function addfile( file ) {
	var stats = fs.lstatSync( file );
	var id = "size_" + stats.size;
	if ( by_size[id] === undefined ) by_size[id] = [];
	by_size[id].push( file );
}

function calc_hash( file ) {
	var shasum = crypto.createHash( 'sha256' );
	var stats = fs.lstatSync( file );
	if ( hash_sampling_percent == 100 || stats.size < 1024*1024 ) {
		shasum.update( fs.readFileSync( file ) );
	} else {
		var buffer = new Buffer( blocksize );
		var block_count = stats.size / blocksize;
		var loop_count = block_count * (hash_sampling_percent / 100);
		var block_space = block_count / loop_count;
		//console.log("file size", stats.size, "block_count", block_count, "loop_count", loop_count, "block_space", block_space);
		var fd = fs.openSync( file, 'r' );
		for (var i=0; i<loop_count; i+=1) {
			var start = ( block_space * i ) * blocksize;
			fs.readSync( fd, buffer, 0, blocksize, start );
			shasum.update( buffer );
		}
		fs.closeSync( fd );
	}
	return shasum.digest( 'hex' );
}

var by_hash = {}
var by_hash_size = 0;
function addfile_by_hash( file, progress_callback ) {
	var hash = calc_hash( file );

	if ( by_hash[hash] === undefined ) by_hash[hash] = [];
	by_hash[hash].push( file );

	by_hash_size += 1;
	progress_callback( by_hash_size );
}

function remove_files( base_path, destination, files ) {
	files.forEach( (file) => {
		var dest_file = file.replace( base_path, destination + '/' );
		var dest_dir = dest_file.split('/').slice(0, -1).join('/');
		if ( dry_run ) {
			console.log("dry_run", file, " => ", dest_file);
		} else {
			fs_extra.mkdirpSync( dest_dir );
			fs.renameSync( file, dest_file );
		}
	});
	console.log('moved', files.length, 'files to "' + dest_dir + '" including directory structure')
}
// ---

console.log("welcome to duper.js: find and delete duplicate (by content) files\n");
console.log("\
usage:\n\tduper.js <directory> [<substring>:<preference>]*\n\n\
with\n\
	<directory> \tthe folder to scan for duplicates\n\
	<substring> \tstring to use for matching filename (incl. path)\n\
	<preference> \t[keep|delete] the action to preferrably take if filename (incl. path) matches <substring>\n\
how it works:\n\n\
	see README.md\n\
");

// parse cmdline args

var dir = process.argv[2];
if ( dir === undefined ) dir = '.';

var prefs =
_.map(
	_.takeRightWhile( process.argv, (a) => {
		return a.indexOf(':') >= 0
	}), (p) => {
		return {
			substring: p.split( ':' )[0],
			preference: p.split( ':' )[1]
		}
	}
);

// scan filesystem recursively, clustering files by size into global by_size

console.log( '\nscanning filesystem...' );
var progress_count = 0;
adddir( dir, (progress) => {
	progress_count += 1;
	if ( progress_count % 50 == 0 ) {
		var pstr = _.map( progress, (p) => {
			return "" + p.current + '/' + p.count;
		}).join( ' ' );
		process.stdout.write( 'scanning progress: ' + pstr + '                                      \r');
	}
});

// filter clusters having more than 1 file

dupes_by_size = _.filter( by_size, (entry) => {
	return entry.length > 1;
});
console.log('found', _.keys( dupes_by_size ).length, 'clusters by size match' );

// hash the duplicate files, clustering by file hash

console.log( '\ncalculating file hashes of files in equally-sized clusters... (using ' + hash_sampling_percent + '% content sampling)' );
var flat_dupes = _.flattenDeep( dupes_by_size );
flat_dupes.forEach( (file) => {
	addfile_by_hash( file, (count) => {
		process.stdout.write( 'hashing progress: ' + count + ' of ' + flat_dupes.length + '\r');
	});
});
process.stdout.write('\n\n');

// filter clusters having more than 1 entry

//console.log("by_hash", by_hash);

var dupes_by_hash = _.filter( by_hash, (entry) => {
	return entry.length > 1;
});

console.log('found', _.keys( dupes_by_hash ).length, 'clusters by hash match' );

//console.log("dupes_by_hash", dupes_by_hash);

// select all except one file per cluster to delete

var deletion_list = _.map( dupes_by_hash, ( cluster ) => {
	//console.log('\n------ checking cluster: ', cluster );

	// map files in each cluster to actions by matching preference substring to filename
	var actions = {
		'delete': [],
		'keep': []
	};

	_.forEach( cluster, (file) => {
		// find first matching preference
		var matching_pref = _.filter( prefs, (pref) => {
			return file.indexOf( pref.substring ) >= 0;
		})[0];
		if ( matching_pref ) {
			actions[matching_pref.preference].push( file );
		}
	})

	// add more items ot 'delete' action until all except one are added
	var quantity_to_add = _.keys(cluster).length - actions['delete'].length - 1
	if ( quantity_to_add > 0 ) {
		// determine array of files neither in 'delete' nor 'keep' (those can be added to 'delete')
		var unmatched = _.filter( cluster, (file) => {
			return (
				actions['delete'].indexOf( file ) == -1 &&
				actions['keep'].indexOf( file ) == -1
			);
		});
		var additional_delete = unmatched.slice( 0, quantity_to_add)
		actions['delete'] = actions['delete'].concat( additional_delete );
		// add the remainder of the unmatched files to 'keep'
		actions['keep'] = actions['keep'].concat( unmatched.slice( quantity_to_add ) );
		// since we made a decision (without using a rule) about which file to delete,
		// we try to achieve some consistency regarding this selection in the future
		// by adding preferences generated from differences between the names of
		// the selected (for deletion) and the non-selected files
		var keep_tokens = _.reduce( actions['keep'], (result, file) => {
			return result.concat( file.split(/[^\w]/) );
		}, []);
		var selected_file_tokens = _.reduce( additional_delete, (result, file) => {
			return result.concat( file.split(/[^\w]/) );
		}, []);
		var delete_tokens = _.difference( selected_file_tokens, keep_tokens );
		// add the identifying tokens as substrings for delete preferences
		delete_tokens.forEach( (token) => {
			if (
				_.map( prefs, ( pref ) => {
					return pref.substring;
				}).indexOf( token ) == -1
			) {
				prefs.push({
					substring: token,
					preference: 'delete'
				});
			}
		});
	}
	// return files mapped to 'delete' action (but not too many)
	return actions['delete'].slice( 0, _.keys(cluster).length - 1 );
});
deletion_list = _.flatten( deletion_list );

console.log("final prefs: ", _.reduce( prefs, (a, p) => { return a + " " + p.substring + ':' + p.preference; }, ""));

//console.log( "will delete the following files:\n" + deletion_list.join('\n') );
//console.log("deletion_list", deletion_list);
console.log("(", deletion_list.length, "files )");

dir = dir.replace(/\/+$/, "");
var dest_dir = dir.split('/').slice(0, -1).join('/') + '/removed_by_duper(' + dir.split('/').slice(-1)[0] + ')';
remove_files( dir, dest_dir, deletion_list );
console.log("", deletion_list.length, "files", (dry_run?"would":""), "have been moved to", dest_dir);<|endoftext|>


<commit_before>/*
# ----------------------------------------------------------------------
# Copyright (C) 2016, Numenta, Inc.  Unless you have an agreement
# with Numenta, Inc., for a separate license for this software code, the
# following terms and conditions apply:
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License version 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with this program.  If not, see http://www.gnu.org/licenses.
#
# http://numenta.org/licenses/
# ----------------------------------------------------------------------
*/

var mathjs = require('mathjs');
var THREE = require('three');
var OBJLoader = require('three-obj-loader');
var TrackballControls = require('three-trackballcontrols');
var ColladaLoader = require('three-collada-loader');

/**
 *
 * @param cells (HtmCells) initial cells to render
 * @param opts (Object) Can contain 'geometry', 'spacing', 'colors', 'elementId'
 * @constructor
 */
function HtmCellVisualization(cells, opts) {
    if (!opts) opts = {};

    this.cells = cells;
    this.meshCells = [];
    this.geometry = opts.geometry;
    this.spacing = opts.spacing;
    this.colors = opts.colors;
    this.width = undefined;
    this.height = undefined;
    this.$container = undefined;
    this.camera = undefined;
    this.controls = undefined;
    this.light = undefined;
    this.scene = undefined;
    this.renderer = undefined;
    this.grid = undefined;
    this.loader = new ColladaLoader();

    this.loader.options.centerGeometry = true;

    // Use a default geometry.
    if (! this.geometry) {
        this.geometry = new THREE.BoxGeometry(1, 1, 1);
    }
    // Use a default spacing.
    if (! this.spacing) {
        this.spacing = 1.25;
    }
    // Use default colors;
    if (! this.colors) {
        this.colors = {
            0: 0xffffff, // white
            1: 0xffff00, // yellow
            2: 0xff0000  // red
        };
    }

    this._setupContainer(opts.elementId);
    this._setupCamera();
    this._setupControls();
    this._setupScene();
}

HtmCellVisualization.prototype._setupContainer = function(elementId) {
    if (elementId) {
        this.$container = $('#' + elementId);
        this.width = this.$container.innerWidth();
        this.height = this.$container.innerHeight();
    } else {
        this.$container = $('body');
        this.width = window.innerWidth;
        this.height = window.innerHeight;
    }
};

HtmCellVisualization.prototype._setupCamera = function() {
    // Set up camera position.
    this.camera = new THREE.PerspectiveCamera(
        60, this.width / this.height, 1, 1000
    );
    this.camera.position.z = 50;
};

HtmCellVisualization.prototype._setupControls = function() {
    this.controls = new TrackballControls(this.camera);
    this.controls.rotateSpeed = 1.0;
    this.controls.zoomSpeed = 1.2;
    this.controls.panSpeed = 0.8;
    this.controls.noZoom = false;
    this.controls.noPan = false;
    this.controls.staticMoving = true;
    this.controls.dynamicDampingFactor = 0.3;
    this.controls.keys = [ 65, 83, 68 ];
};

HtmCellVisualization.prototype._setupScene = function() {
    var scene;
    var renderer;

    this.scene = new THREE.Scene();
    scene = this.scene;
    scene.fog = new THREE.FogExp2(0xEEEEEE, 0.002);

    this.light = new THREE.PointLight(0xFFFFFF);
    scene.add(this.light);

    this.renderer = new THREE.WebGLRenderer({antialias: false});
    renderer = this.renderer;
    renderer.setClearColor(scene.fog.color);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(this.width, this.height);
};

/*
 * Creates all the geometries within the grid. These are only created once and
 * updated as cells change over time, so this function should only be called
 * one time.
 */
HtmCellVisualization.prototype._createMeshCells = function() {
    var scene = this.scene;
    var colors = this.colors;
    var cells = this.cells;
    var meshCells = this.meshCells;
    var spacing = this.spacing;
    var x = cells.getX();
    var y = cells.getY();
    var z = cells.getZ();
    var initialXOffset = (spacing * x) / 2;
    var initialYOffset = (spacing * y) / 2;
    var initialZOffset = (spacing * z) / 2;
    var material, cube, zdim, xdim;

    if (! this.grid) {
        this.grid = new THREE.Group();
        for (var cy = 0; cy < y; cy++) {
            zdim = [];
            for (var cz = 0; cz < z; cz++) {
                xdim = [];
                for (var cx = 0; cx < x; cx++) {
                    material = new THREE.MeshLambertMaterial(
                        {color: colors[cells.getCellValue(cx, cy, cz)]}
                    );
                    cube = new THREE.Mesh(this.geometry, material);
                    cube.position.x = spacing * cz - initialYOffset;
                    cube.position.y = spacing * cx - initialXOffset;
                    cube.position.z = spacing * cy - initialZOffset;
                    cube.updateMatrix();
                    cube.matrixAutoUpdate = false;
                    this.grid.add(cube);
                    xdim.push(cube);
                }
                zdim.push(xdim);
            }
            meshCells.push(zdim);
        }
    }

    //this.grid.rotation.z = -45 * mathjs.PI / 180;
    //this.grid.rotation.x = -60 * mathjs.PI / 180;

    //this.grid.rotation.x = -60 * mathjs.PI / 180;
    this.grid.position.x = -30;
    //this.grid.position.y = -100;
    //this.grid.position.z = -100;

    scene.add(this.grid);
};

/*
 * Updates the mesh cell colors based on the cells, which might have changed.
 * This function should only be called when the cells change.
 */
HtmCellVisualization.prototype._applyMeshCells = function() {
    var colors = this.colors;
    var cells = this.cells;
    var meshCells = this.meshCells;
    var x = cells.getX();
    var y = cells.getY();
    var z = cells.getZ();
    var cube;
    for (var cy = 0; cy < y; cy++) {
        for (var cz = 0; cz < z; cz++) {
            for (var cx = 0; cx < x; cx++) {
                cube = meshCells[cy][cz][cx];
                cube.material.color = new THREE.Color(
                    colors[cells.getCellValue(cx, cy, cz)]
                );
            }
        }
    }
};

/**
 * Called once to render the canvas into the DOM with the initial cell data.
 */
HtmCellVisualization.prototype.render = function() {
    var me = this;
    var renderer = this.renderer;
    var scene = this.scene;
    var controls = this.controls;
    var camera = this.camera;
    var light = this.light;
    var w = this.width;
    var h = this.height;

    this._createMeshCells();

    renderer = new THREE.WebGLRenderer( { antialias: false } );

    renderer.setClearColor( scene.fog.color );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize(w, h);

    function innerRender() {
        light.position.x = camera.position.x;
        light.position.y = camera.position.y;
        light.position.z = camera.position.z;
        renderer.render(scene, camera);
    }

    this.controls.addEventListener('change', innerRender);

    window.addEventListener('resize', function() {
        w = me.width = me.$container.innerWidth();
        h = me.height = me.$container.innerHeight();
        camera.aspect = w / h;
        camera.updateProjectionMatrix();
        renderer.setSize(w, h);
        controls.handleResize();
        innerRender();
    }, false );

    this.$container.append(renderer.domElement);

    function animate() {
        requestAnimationFrame(animate);
        me.controls.update();
        innerRender();
    }

    setTimeout(animate, 0);

    camera.position.z = 20;
};

HtmCellVisualization.prototype.redraw = function() {
    this._applyMeshCells();
};

/**
 * This interface is used to update cell data within the HtmCellVisualization.
 * Once created, use it to update cell values.
 * @param x (int) x dimension
 * @param y (int) y dimension
 * @param z (int) z dimension
 * @constructor
 */
function HtmCells(x, y, z) {
    this.xdim = x;
    this.ydim = y;
    this.zdim = z;
    this.cells = [];
    // Create initially empty matrices.
    for (var cy = 0; cy < y; cy++) {
        this.cells.push(mathjs.zeros(x, z, 'sparse'));
    }
}

HtmCells.prototype.getX = function() {
    return this.xdim;
};

HtmCells.prototype.getY = function() {
    return this.ydim;
};

HtmCells.prototype.getZ = function() {
    return this.zdim;
};

/**
 * Gets the value of the cell given the coordinates.
 * @param x (int) x coordinate
 * @param y (int) y coordinate
 * @param z (int) z coordinate
 * @returns {*} whatever value was in the cell
 */
HtmCells.prototype.getCellValue = function(x, y, z) {
    // TODO: raise error if cell coordinates are invalid.
    return this.cells[y].subset(mathjs.index(x, z));
};

/**
 * Allows user to update a cell's value.
 * @param x (int) x coordinate
 * @param y (int) y coordinate
 * @param z (int) z coordinate
 * @param value {*} Whatever value you want the cell to have.
 */
HtmCells.prototype.update = function(x, y, z, value) {
    // TODO: raise error if cell coordinates are invalid.
    this.cells[y].subset(mathjs.index(x, z), value);
};

window.HtmCellVisualization = HtmCellVisualization;
window.HtmCells = HtmCells;
<commit_msg>Fixed bad xyz positioning.<commit_after>/*
# ----------------------------------------------------------------------
# Copyright (C) 2016, Numenta, Inc.  Unless you have an agreement
# with Numenta, Inc., for a separate license for this software code, the
# following terms and conditions apply:
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License version 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with this program.  If not, see http://www.gnu.org/licenses.
#
# http://numenta.org/licenses/
# ----------------------------------------------------------------------
*/

var mathjs = require('mathjs');
var THREE = require('three');
var OBJLoader = require('three-obj-loader');
var TrackballControls = require('three-trackballcontrols');
var ColladaLoader = require('three-collada-loader');

/**
 *
 * @param cells (HtmCells) initial cells to render
 * @param opts (Object) Can contain 'geometry', 'spacing', 'colors', 'elementId'
 * @constructor
 */
function HtmCellVisualization(cells, opts) {
    if (!opts) opts = {};

    this.cells = cells;
    this.meshCells = [];
    this.geometry = opts.geometry;
    this.spacing = opts.spacing;
    this.colors = opts.colors;
    this.width = undefined;
    this.height = undefined;
    this.$container = undefined;
    this.camera = undefined;
    this.controls = undefined;
    this.light = undefined;
    this.scene = undefined;
    this.renderer = undefined;
    this.grid = undefined;
    this.loader = new ColladaLoader();

    this.loader.options.centerGeometry = true;

    // Use a default geometry.
    if (! this.geometry) {
        this.geometry = new THREE.BoxGeometry(1, 1, 1);
    }
    // Use a default spacing.
    if (! this.spacing) {
        this.spacing = 1.25;
    }
    // Use default colors;
    if (! this.colors) {
        this.colors = {
            0: 0xffffff, // white
            1: 0xffff00, // yellow
            2: 0xff0000  // red
        };
    }

    this._setupContainer(opts.elementId);
    this._setupCamera();
    this._setupControls();
    this._setupScene();
}

HtmCellVisualization.prototype._setupContainer = function(elementId) {
    if (elementId) {
        this.$container = $('#' + elementId);
        this.width = this.$container.innerWidth();
        this.height = this.$container.innerHeight();
    } else {
        this.$container = $('body');
        this.width = window.innerWidth;
        this.height = window.innerHeight;
    }
};

HtmCellVisualization.prototype._setupCamera = function() {
    // Set up camera position.
    this.camera = new THREE.PerspectiveCamera(
        60, this.width / this.height, 1, 1000
    );
    this.camera.position.z = 50;
};

HtmCellVisualization.prototype._setupControls = function() {
    this.controls = new TrackballControls(this.camera);
    this.controls.rotateSpeed = 1.0;
    this.controls.zoomSpeed = 1.2;
    this.controls.panSpeed = 0.8;
    this.controls.noZoom = false;
    this.controls.noPan = false;
    this.controls.staticMoving = true;
    this.controls.dynamicDampingFactor = 0.3;
    this.controls.keys = [ 65, 83, 68 ];
};

HtmCellVisualization.prototype._setupScene = function() {
    var scene;
    var renderer;

    this.scene = new THREE.Scene();
    scene = this.scene;
    scene.fog = new THREE.FogExp2(0xEEEEEE, 0.002);

    this.light = new THREE.PointLight(0xFFFFFF);
    scene.add(this.light);

    this.renderer = new THREE.WebGLRenderer({antialias: false});
    renderer = this.renderer;
    renderer.setClearColor(scene.fog.color);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(this.width, this.height);
};

/*
 * Creates all the geometries within the grid. These are only created once and
 * updated as cells change over time, so this function should only be called
 * one time.
 */
HtmCellVisualization.prototype._createMeshCells = function(position, rotation) {
    var scene = this.scene;
    var colors = this.colors;
    var cells = this.cells;
    var meshCells = this.meshCells;
    var spacing = this.spacing;
    var x = cells.getX();
    var y = cells.getY();
    var z = cells.getZ();
    var initialXOffset = (spacing * x) / 2;
    var initialYOffset = (spacing * y) / 2;
    var initialZOffset = (spacing * z) / 2;
    var material, cube, zdim, xdim;

    if (! this.grid) {
        this.grid = new THREE.Group();
        for (var cy = 0; cy < y; cy++) {
            xdim = [];
            for (var cx = 0; cx < x; cx++) {
                zdim = [];
                for (var cz = 0; cz < z; cz++) {
                    material = new THREE.MeshLambertMaterial(
                        {color: colors[cells.getCellValue(cx, cy, cz)]}
                    );
                    cube = new THREE.Mesh(this.geometry, material);
                    cube.position.y = spacing * cy - initialYOffset;
                    cube.position.x = spacing * cx - initialXOffset;
                    cube.position.z = spacing * cz - initialZOffset;
                    cube.updateMatrix();
                    cube.matrixAutoUpdate = false;
                    this.grid.add(cube);
                    zdim.push(cube);
                }
                xdim.push(zdim);
            }
            meshCells.push(xdim);
        }
    }

    if (position) {
        if (position.x) this.grid.position.x = position.x;
        if (position.y) this.grid.position.y = position.y;
        if (position.z) this.grid.position.z = position.z;
    }
    if (rotation) {
        if (rotation.x) this.grid.rotation.x = rotation.x;
        if (rotation.y) this.grid.rotation.y = rotation.y;
        if (rotation.z) this.grid.rotation.z = rotation.z;
    }

    scene.add(this.grid);
};

/*
 * Updates the mesh cell colors based on the cells, which might have changed.
 * This function should only be called when the cells change.
 */
HtmCellVisualization.prototype._applyMeshCells = function() {
    var colors = this.colors;
    var cells = this.cells;
    var meshCells = this.meshCells;
    var x = cells.getX();
    var y = cells.getY();
    var z = cells.getZ();
    var cube;
    for (var cy = 0; cy < y; cy++) {
        for (var cx = 0; cx < x; cx++) {
            for (var cz = 0; cz < z; cz++) {
                cube = meshCells[cy][cx][cz];
                cube.material.color = new THREE.Color(
                    colors[cells.getCellValue(cx, cy, cz)]
                );
            }
        }
    }
};

/**
 * Called once to render the canvas into the DOM with the initial cell data.
 */
HtmCellVisualization.prototype.render = function(opts) {
    if (!opts) opts = {};
    var me = this;
    var renderer = this.renderer;
    var scene = this.scene;
    var controls = this.controls;
    var camera = this.camera;
    var light = this.light;
    var w = this.width;
    var h = this.height;

    this._createMeshCells(opts.position, opts.rotation);

    renderer = new THREE.WebGLRenderer( { antialias: false } );

    renderer.setClearColor( scene.fog.color );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize(w, h);

    function innerRender() {
        light.position.x = camera.position.x;
        light.position.y = camera.position.y;
        light.position.z = camera.position.z;
        renderer.render(scene, camera);
    }

    this.controls.addEventListener('change', innerRender);

    window.addEventListener('resize', function() {
        w = me.width = me.$container.innerWidth();
        h = me.height = me.$container.innerHeight();
        camera.aspect = w / h;
        camera.updateProjectionMatrix();
        renderer.setSize(w, h);
        controls.handleResize();
        innerRender();
    }, false );

    this.$container.append(renderer.domElement);

    function animate() {
        requestAnimationFrame(animate);
        me.controls.update();
        innerRender();
    }

    setTimeout(animate, 0);

    if (opts.camera) {
        if (opts.camera.x) camera.position.x = opts.camera.x;
        if (opts.camera.y) camera.position.y = opts.camera.y;
        if (opts.camera.z) camera.position.z = opts.camera.z;
    }
};

HtmCellVisualization.prototype.redraw = function() {
    this._applyMeshCells();
};

/**
 * This interface is used to update cell data within the HtmCellVisualization.
 * Once created, use it to update cell values.
 * @param x (int) x dimension
 * @param y (int) y dimension
 * @param z (int) z dimension
 * @constructor
 */
function HtmCells(x, y, z) {
    this.xdim = x;
    this.ydim = y;
    this.zdim = z;
    this.cells = [];
    // Create initially empty matrices.
    for (var cy = 0; cy < y; cy++) {
        this.cells.push(mathjs.zeros(x, z, 'sparse'));
    }
}

HtmCells.prototype.getX = function() {
    return this.xdim;
};

HtmCells.prototype.getY = function() {
    return this.ydim;
};

HtmCells.prototype.getZ = function() {
    return this.zdim;
};

/**
 * Gets the value of the cell given the coordinates.
 * @param x (int) x coordinate
 * @param y (int) y coordinate
 * @param z (int) z coordinate
 * @returns {*} whatever value was in the cell
 */
HtmCells.prototype.getCellValue = function(x, y, z) {
    // TODO: raise error if cell coordinates are invalid.
    return this.cells[y].subset(mathjs.index(x, z));
};

/**
 * Allows user to update a cell's value.
 * @param x (int) x coordinate
 * @param y (int) y coordinate
 * @param z (int) z coordinate
 * @param value {*} Whatever value you want the cell to have.
 */
HtmCells.prototype.update = function(x, y, z, value) {
    // TODO: raise error if cell coordinates are invalid.
    this.cells[y].subset(mathjs.index(x, z), value);
};

window.HtmCellVisualization = HtmCellVisualization;
window.HtmCells = HtmCells;
<|endoftext|>


<commit_before>var request = require("request")

var GITHUB_USERNAME = process.env.AI_GITHUB_USERNAME
  , OLDISSUE = new Date() - (1000 * 60 * 60 * 24 * 15)
  , now = new Date()

function getIssues (url, user, repo, cb) {
  var aiUrl = url || "https://api.github.com/search/issues" +
    "?q=commenter:" + GITHUB_USERNAME + "+state:open+user:" +
    user + "+repo:" + repo

  request({
    json: true,
    url: aiUrl,
    headers: {
      "User-Agent": "Abandoned issue script"
    }
  }, function (er, res, body) {
    if (!body || !body.items) return console.error("\nNo issues found.")
    filter(body.items, user, cb)
  })
}

function filter (issues, user, cb) {
  var i = issues.filter(function (el) {
    var match = new RegExp(user + "/" + "issues").test(el.html_url)
    var old = new Date(el.updated_at).getTime() < OLDISSUE
    return match && old
  })
  return cb(i)
}

module.exports = getIssues
<commit_msg>remove superfluous now<commit_after>var request = require("request")

var GITHUB_USERNAME = process.env.AI_GITHUB_USERNAME
  , OLDISSUE = new Date() - (1000 * 60 * 60 * 24 * 15)

function getIssues (url, user, repo, cb) {
  var aiUrl = url || "https://api.github.com/search/issues" +
    "?q=commenter:" + GITHUB_USERNAME + "+state:open+user:" +
    user + "+repo:" + repo

  request({
    json: true,
    url: aiUrl,
    headers: {
      "User-Agent": "Abandoned issue script"
    }
  }, function (er, res, body) {
    if (!body || !body.items) return console.error("\nNo issues found.")
    filter(body.items, user, cb)
  })
}

function filter (issues, user, cb) {
  var i = issues.filter(function (el) {
    var match = new RegExp(user + "/" + "issues").test(el.html_url)
    var old = new Date(el.updated_at).getTime() < OLDISSUE
    return match && old
  })
  return cb(i)
}

module.exports = getIssues
<|endoftext|>


<commit_before>'use strict';
var Foxx = require('org/arangodb/foxx');
var controller = new Foxx.Controller(applicationContext);
var Credentials = require('./models/credentials');
var UserProfile = require('./models/userProfile');

controller.activateAuthentication({
  sessionStorageApp: 'sessions',
  cookieName: 'sid',
  type: 'cookie'
});

function getAuthenticator() {
  return Foxx.requireApp('auth').auth;
}

function getUserStorage() {
  return Foxx.requireApp('users').userStorage;
}

function NotAnAdmin() {}
NotAnAdmin.prototype = new Error();

function isAdmin(req) {
  var userData = req.session.get('userData');
  if (!userData || !userData.admin) throw new NotAnAdmin();
}

/** Login route
 *
 * Attempts to log the user in using username and password auth.
 */
controller.post('/login', function(req, res) {
  var credentials = req.params('credentials');
  var user = getAuthenticator().login(
    req.session,
    getUserStorage().resolve(credentials.get('username')),
    credentials.get('password')
  );
  if (user) {
    req.session.save();
    res.json({success: true, user: user.get('userData')});
  } else {
    res.status(403);
    res.json({success: false, error: 'Invalid password or unknown username.'});
  }
})
.bodyParam('credentials', 'Authentication credentials.', Credentials)
.summary('AuthenticuserProfile')
.notes('Attempts to log the user in with username and password.');

/** Registration route
 *
 * Demonstrates creating a new account and logging the user in manually.
 */
controller.post('/register', function(req, res) {
  var credentials = req.params('credentials');
  var userProfile = req.params('profile');
  var userData = userProfile.forDB();
  userData.username = credentials.get('username');
  var users = getUserStorage();
  var user = users.create(userData);
  getAuthenticator().setPassword(user, credentials.get('password'));
  user.save();
  // now log the user in
  req.session.setUser(user);
  req.session.save();
  res.json({success: true, user: user.get('userData'), users: users.list()});
})
.bodyParam('credentials', 'Username and password', Credentials)
.bodyParam('profile', 'User profile data', UserProfile)
.summary('Register')
.notes('Create a new account and log the user in.');

/** Logout route
 *
 * Logs the user out by deleting their session. Always creates a new
 * session to make sure cookies are overwritten.
 */
controller.logout('/logout', function(req, res) {
  res.json({success: true});
})
.summary('De-authenticate')
.notes('Wipes the session data for the active session.');

/** Registered user list
 *
 * Demonstrates fetching the list of usernames from the user storage.
 */
controller.get('/users', function(req, res) {
  res.json({users: getUserStorage().list()});
})
.summary('Registered users')
.notes('Returns a list of all known usernames.');

/** Get current user
 *
 * If the user is logged in, returns the user's userData. Otherwise
 * returns null for the userData.
 */
controller.get('/whoami', function(req, res) {
  if (!req.session.get('uid')) {
    res.json({user: null});
  } else {
    res.json({user: req.session.get('userData') || {}});
  }
})
.summary('Session user status')
.notes('Returns the active user or null.');

/** Counter
 *
 * Demonstrates storing and updating a value in the session.
 * Also shows how to restrict a route to logged-in users.
 */
controller.get('/counter', function(req, res) {
  var sdata = req.session.get('sessionData');
  if (sdata.counter) sdata.counter++;
  else sdata.counter = 1;
  req.session.set('sessionData', sdata);
  req.session.save();
  res.json({counter: sdata.counter});
})
.onlyIfAuthenticated()
.summary('Get and increment a counter')
.notes('Fetches the number of times this routes has been called in this session.');

/** Session dump
 *
 * Dumps the content of the session object. Also demonstrates how to
 * restrict a route to admin users.
 */
controller.get('/dump', function(req, res) {
  res.json(req.session.forClient());
})
.onlyIf(isAdmin)
.errorResponse(NotAnAdmin, 403, 'You are not an admin.')
.summary('Dump session object')
.notes('Returns the session object. Demonstrates restricting route access.');
<commit_msg>Added cookieSecret param.<commit_after>'use strict';
var Foxx = require('org/arangodb/foxx');
var controller = new Foxx.Controller(applicationContext);
var Credentials = require('./models/credentials');
var UserProfile = require('./models/userProfile');

controller.activateAuthentication({
  sessionStorageApp: 'sessions',
  cookieName: 'sid',
  cookieSecret: 'secret',
  type: 'cookie'
});

function getAuthenticator() {
  return Foxx.requireApp('auth').auth;
}

function getUserStorage() {
  return Foxx.requireApp('users').userStorage;
}

function NotAnAdmin() {}
NotAnAdmin.prototype = new Error();

function isAdmin(req) {
  var userData = req.session.get('userData');
  if (!userData || !userData.admin) throw new NotAnAdmin();
}

/** Login route
 *
 * Attempts to log the user in using username and password auth.
 */
controller.post('/login', function(req, res) {
  var credentials = req.params('credentials');
  var user = getAuthenticator().login(
    req.session,
    getUserStorage().resolve(credentials.get('username')),
    credentials.get('password')
  );
  if (user) {
    req.session.save();
    res.json({success: true, user: user.get('userData')});
  } else {
    res.status(403);
    res.json({success: false, error: 'Invalid password or unknown username.'});
  }
})
.bodyParam('credentials', 'Authentication credentials.', Credentials)
.summary('AuthenticuserProfile')
.notes('Attempts to log the user in with username and password.');

/** Registration route
 *
 * Demonstrates creating a new account and logging the user in manually.
 */
controller.post('/register', function(req, res) {
  var credentials = req.params('credentials');
  var userProfile = req.params('profile');
  var userData = userProfile.forDB();
  userData.username = credentials.get('username');
  var users = getUserStorage();
  var user = users.create(userData);
  getAuthenticator().setPassword(user, credentials.get('password'));
  user.save();
  // now log the user in
  req.session.setUser(user);
  req.session.save();
  res.json({success: true, user: user.get('userData'), users: users.list()});
})
.bodyParam('credentials', 'Username and password', Credentials)
.bodyParam('profile', 'User profile data', UserProfile)
.summary('Register')
.notes('Create a new account and log the user in.');

/** Logout route
 *
 * Logs the user out by deleting their session. Always creates a new
 * session to make sure cookies are overwritten.
 */
controller.logout('/logout', function(req, res) {
  res.json({success: true});
})
.summary('De-authenticate')
.notes('Wipes the session data for the active session.');

/** Registered user list
 *
 * Demonstrates fetching the list of usernames from the user storage.
 */
controller.get('/users', function(req, res) {
  res.json({users: getUserStorage().list()});
})
.summary('Registered users')
.notes('Returns a list of all known usernames.');

/** Get current user
 *
 * If the user is logged in, returns the user's userData. Otherwise
 * returns null for the userData.
 */
controller.get('/whoami', function(req, res) {
  if (!req.session.get('uid')) {
    res.json({user: null});
  } else {
    res.json({user: req.session.get('userData') || {}});
  }
})
.summary('Session user status')
.notes('Returns the active user or null.');

/** Counter
 *
 * Demonstrates storing and updating a value in the session.
 * Also shows how to restrict a route to logged-in users.
 */
controller.get('/counter', function(req, res) {
  var sdata = req.session.get('sessionData');
  if (sdata.counter) sdata.counter++;
  else sdata.counter = 1;
  req.session.set('sessionData', sdata);
  req.session.save();
  res.json({counter: sdata.counter});
})
.onlyIfAuthenticated()
.summary('Get and increment a counter')
.notes('Fetches the number of times this routes has been called in this session.');

/** Session dump
 *
 * Dumps the content of the session object. Also demonstrates how to
 * restrict a route to admin users.
 */
controller.get('/dump', function(req, res) {
  res.json(req.session.forClient());
})
.onlyIf(isAdmin)
.errorResponse(NotAnAdmin, 403, 'You are not an admin.')
.summary('Dump session object')
.notes('Returns the session object. Demonstrates restricting route access.');
<|endoftext|>


<commit_before>const h = require("snabbdom/h")
const html = require("hyperx")((tagName, attrs, children) => {
    const keys = Object.keys(attrs), data = {
        class: {}, props: {}, style: {}, attrs: {}, on: {}
    }

    let i = keys.length

    while (~--i) {
        const key = keys[i], value = attrs[key]

        if (key === undefined) {
            continue
        }

        if (key === "className") {
            value.split(" ").forEach(cls => data.class[cls] = true)

        } else if (key === "style") {
            data.style = value

        } else if (0 === key.indexOf("on")) {
            data.on[key.substr(2)] = value

        } else {
            data.props[key] = data.attrs[key] = value === "true"
                ? true : value === "false" ? false : value
        }
    }

    return h(tagName, data, children ? [].concat(...children) : children)
})
const snabbdom = require("snabbdom")
const patch = snabbdom.init([
    require("snabbdom/modules/class"),
    require("snabbdom/modules/props"),
    require("snabbdom/modules/style"),
    require("snabbdom/modules/attributes"),
    require("snabbdom/modules/eventlisteners")
])

const app = (model, view, reducers, subscriptions, container) => {
    if (view === undefined) {
        view = model.view
        reducers = model.reducers || model.update
        subscriptions = model.subscriptions
        container = model.container
        model = model.model
    }

    const update = typeof reducers === "function"
        ? reducers
        : (model, msg) => {
            const type = msg.type === undefined ? msg : msg.type
            return reducers[type] === undefined
                ? model
                : reducers[type](model, msg)
        }

    const render = (model, view, update, lastNode) => {
        const send = msg => render(update(model, msg), view, update, nextNode)
        const nextNode = view(model, send)

        if (subscriptions)
            document.addEventListener("DOMContentLoaded", _ =>
                Object.keys(subscriptions).forEach(key => subscriptions[key](send)))

        patch(lastNode, nextNode)
    }

    return render(model, view, update, container
        ? container
        : document.body.appendChild(document.createElement("div"))
    )
}

module.exports = { html, app }
<commit_msg>Mutate model and container / nextNode reference immediately.<commit_after>const h = require("snabbdom/h")
const html = require("hyperx")((tagName, attrs, children) => {
    const keys = Object.keys(attrs), data = {
        class: {}, props: {}, style: {}, attrs: {}, on: {}
    }

    let i = keys.length

    while (~--i) {
        const key = keys[i], value = attrs[key]

        if (key === undefined) {
            continue
        }

        if (key === "className") {
            value.split(" ").forEach(cls => data.class[cls] = true)

        } else if (key === "style") {
            data.style = value

        } else if (0 === key.indexOf("on")) {
            data.on[key.substr(2)] = value

        } else {
            data.props[key] = data.attrs[key] = value === "true"
                ? true : value === "false" ? false : value
        }
    }

    return h(tagName, data, children ? [].concat(...children) : children)
})
const snabbdom = require("snabbdom")
const patch = snabbdom.init([
    require("snabbdom/modules/class"),
    require("snabbdom/modules/props"),
    require("snabbdom/modules/style"),
    require("snabbdom/modules/attributes"),
    require("snabbdom/modules/eventlisteners")
])

const app = (model, view, reducers, subs, container) => {
    if (view === undefined) {
        view = model.view
        reducers = model.reducers || model.update
        subs = model.subs || model.subscriptions
        container = model.container
        model = model.model
    }

    const update = typeof reducers === "function"
        ? reducers : (model, msg, data) => reducers[msg](model, data)

    const render = (model, view, update, lastNode) => {
        patch(lastNode, container = view(model, dispatch))
        if (subs) {
            document.addEventListener("DOMContentLoaded", _ =>
                Object.keys(subs).forEach(key => subs[key](dispatch, model)))
        }
    }

    const dispatch = (msg, data) =>
        render(model = update(model, msg, data), view, update, container)

    return render(model, view, update, container
        ? container : document.body.appendChild(document.createElement("div"))
    )
}

module.exports = { html, app }

<|endoftext|>


<commit_before>'use strict'

const shasum = require( 'shasum' )
const spawn = require( 'child_process' ).spawn
const path = require( 'path' )
const fs = require( 'fs' )

const PACKAGE_NAME_EXTRACT_EXP = RegExp( '(@(.*)\/)?(.*)' )
const NPM_BIN_PATH = `/usr/local/bin/npm`

module.exports = {
  createPackageName: createPackageName,
  createImageName: createImageName,
  init: init,
  dockerBuild: dockerBuild,
  dockerPush: dockerPush,
  dockerRun: dockerRun
}

/**
 * Create package name based on scope/name or overriden-scope/name
 * @param {Object} packageConfig
 * @return {string}
 */
function createPackageName( packageConfig ) {

  // Parse package name
  const packageConfigNameList = PACKAGE_NAME_EXTRACT_EXP.exec( packageConfig.name )

  const packageConfigName = packageConfigNameList[ 3 ]

  let packageConfigScope = packageConfigNameList[ 2 ]

  if (
    packageConfig.config &&
    packageConfig.config.dockerScope
  ) {

    packageConfigScope = packageConfig.config.dockerScope

  }

  if (
    packageConfigScope === `` ||
    packageConfigScope === undefined
  ) {

    return packageConfigName

  }

  return [
    packageConfigScope,
    packageConfigName
  ].join( '/' )

}

/**
 * Create Docker image name from package.json
 * - create shasum from whole package.json string
 * - get proper package name
 * @param {Object} packageConfig
 * @return {string}
 */
function createImageName( packageConfig ) {

  const packageConfigHash = shasum( JSON.stringify( packageConfig ) )

  const packageConfigName = createPackageName( packageConfig )

  return `${packageConfigName}:${packageConfigHash}`

}

/**
 * Copy Dockerfile and .dockerignore to project structure
 */
function init() {

  copyFile( 'Dockerfile' )
  copyFile( '.dockerignore' )

}

/**
 * Copy dockerfile to project structure
 */
function copyFile( fileName ) {

  const targetFilePath = path.resolve( process.env.PWD, fileName )
  const writeStream = fs.createWriteStream( targetFilePath )
  writeStream.on( 'error', _reportError )

  const sourceFilePath = path.resolve( __dirname, fileName )
  const readStream = fs.createReadStream( sourceFilePath ).pipe( writeStream )
  readStream.on( 'error', _reportError )

  function _reportError( err ) {

    throw Error( err )

  }

}

/**
 * Spawn subprocess with specified command
 * @param {string} cmd command name to execute
 * @param {string[]} args arguments for executed command
 */
function execCommand( cmd, args ) {

  console.log( `EXEC: ${cmd} ${args.join( ' ' )}` )

  return spawn( cmd, args, {
    env: process.env,
    cwd: process.env.PWD,
    stdio: [ 'inherit', process.stdout, process.stdout ]
  } )

}

/**
 * Execute docker build command
 * @param {Object} packageConfig
 */
function dockerBuild( packageConfig ) {

  const proc = `docker`
  const args = [
    `build`,
    `-t`,
    `${createImageName( packageConfig )}`,
    `.`
  ]

  return execCommand( proc, args )

}

/**
 * Execute docker push command
 * @param {Object} packageConfig
 */
function dockerPush( packageConfig ) {

  const proc = `docker`
  const args = [
    `push`,
    `${createImageName( packageConfig )}`
  ]

  return execCommand( proc, args )

}

/**
 * Run docker run with specified command
 * @param {Object} packageConfig
 * @param {string[]} command
 */
function dockerRun( packageConfig, cmdArgs ) {

  const proc = `docker`

  let args = [
    `run`,
    `--volume=${ process.cwd() }:/srv`,
    `--volume=/data/${ createPackageName( packageConfig ) }:/data/${ createPackageName( packageConfig ) }`,
    `--interactive`,
    `--tty`
  ]

  // Forward port if defined in package.json or in commandline
  if (
    ( packageConfig.config && packageConfig.config.port ) ||
    cmdArgs.port
  ) {

    const port = ( cmdArgs.port || packageConfig.config.port )

    args = args.concat(
      '-p',
      `${port}:${port}`
    )

  }

  args = args
    .concat( `${createImageName( packageConfig )}` )

  if (
    cmdArgs.command !== undefined &&
    cmdArgs.command.length
  ) {

    args = args.concat( cmdArgs.command )

  }

  return execCommand( proc, args )

}<commit_msg>fix(Run): remove data mount<commit_after>'use strict'

const shasum = require( 'shasum' )
const spawn = require( 'child_process' ).spawn
const path = require( 'path' )
const fs = require( 'fs' )

const PACKAGE_NAME_EXTRACT_EXP = RegExp( '(@(.*)\/)?(.*)' )
const NPM_BIN_PATH = `/usr/local/bin/npm`

module.exports = {
  createPackageName: createPackageName,
  createImageName: createImageName,
  init: init,
  dockerBuild: dockerBuild,
  dockerPush: dockerPush,
  dockerRun: dockerRun
}

/**
 * Create package name based on scope/name or overriden-scope/name
 * @param {Object} packageConfig
 * @return {string}
 */
function createPackageName( packageConfig ) {

  // Parse package name
  const packageConfigNameList = PACKAGE_NAME_EXTRACT_EXP.exec( packageConfig.name )

  const packageConfigName = packageConfigNameList[ 3 ]

  let packageConfigScope = packageConfigNameList[ 2 ]

  if (
    packageConfig.config &&
    packageConfig.config.dockerScope
  ) {

    packageConfigScope = packageConfig.config.dockerScope

  }

  if (
    packageConfigScope === `` ||
    packageConfigScope === undefined
  ) {

    return packageConfigName

  }

  return [
    packageConfigScope,
    packageConfigName
  ].join( '/' )

}

/**
 * Create Docker image name from package.json
 * - create shasum from whole package.json string
 * - get proper package name
 * @param {Object} packageConfig
 * @return {string}
 */
function createImageName( packageConfig ) {

  const packageConfigHash = shasum( JSON.stringify( packageConfig ) )

  const packageConfigName = createPackageName( packageConfig )

  return `${packageConfigName}:${packageConfigHash}`

}

/**
 * Copy Dockerfile and .dockerignore to project structure
 */
function init() {

  copyFile( 'Dockerfile' )
  copyFile( '.dockerignore' )

}

/**
 * Copy dockerfile to project structure
 */
function copyFile( fileName ) {

  const targetFilePath = path.resolve( process.env.PWD, fileName )
  const writeStream = fs.createWriteStream( targetFilePath )
  writeStream.on( 'error', _reportError )

  const sourceFilePath = path.resolve( __dirname, fileName )
  const readStream = fs.createReadStream( sourceFilePath ).pipe( writeStream )
  readStream.on( 'error', _reportError )

  function _reportError( err ) {

    throw Error( err )

  }

}

/**
 * Spawn subprocess with specified command
 * @param {string} cmd command name to execute
 * @param {string[]} args arguments for executed command
 */
function execCommand( cmd, args ) {

  console.log( `EXEC: ${cmd} ${args.join( ' ' )}` )

  return spawn( cmd, args, {
    env: process.env,
    cwd: process.env.PWD,
    stdio: [ 'inherit', process.stdout, process.stdout ]
  } )

}

/**
 * Execute docker build command
 * @param {Object} packageConfig
 */
function dockerBuild( packageConfig ) {

  const proc = `docker`
  const args = [
    `build`,
    `-t`,
    `${createImageName( packageConfig )}`,
    `.`
  ]

  return execCommand( proc, args )

}

/**
 * Execute docker push command
 * @param {Object} packageConfig
 */
function dockerPush( packageConfig ) {

  const proc = `docker`
  const args = [
    `push`,
    `${createImageName( packageConfig )}`
  ]

  return execCommand( proc, args )

}

/**
 * Run docker run with specified command
 * @param {Object} packageConfig
 * @param {string[]} command
 */
function dockerRun( packageConfig, cmdArgs ) {

  const proc = `docker`

  let args = [
    `run`,
    `--volume=${ process.cwd() }:/srv`,
    `--interactive`,
    `--tty`
  ]

  // Forward port if defined in package.json or in commandline
  if (
    ( packageConfig.config && packageConfig.config.port ) ||
    cmdArgs.port
  ) {

    const port = ( cmdArgs.port || packageConfig.config.port )

    args = args.concat(
      '-p',
      `${port}:${port}`
    )

  }

  args = args
    .concat( `${createImageName( packageConfig )}` )

  if (
    cmdArgs.command !== undefined &&
    cmdArgs.command.length
  ) {

    args = args.concat( cmdArgs.command )

  }

  return execCommand( proc, args )

}
<|endoftext|>


<commit_before><commit_msg>reject on error<commit_after><|endoftext|>


<commit_before><commit_msg>Include buffer cast.<commit_after><|endoftext|>


<commit_before><commit_msg>customize tip to work with our lib<commit_after><|endoftext|>


<commit_before><commit_msg>more fix<commit_after><|endoftext|>


<commit_before><commit_msg>fixed<commit_after><|endoftext|>


<commit_before><commit_msg>Back to the usual workaround<commit_after><|endoftext|>


<commit_before><commit_msg>Convert to ES5<commit_after><|endoftext|>


<commit_before><commit_msg>eslint fixes<commit_after><|endoftext|>


<commit_before><commit_msg>Convert functions into object methods<commit_after><|endoftext|>


<commit_before><commit_msg>Optimize observe()<commit_after><|endoftext|>


<commit_before><commit_msg>added link to public file<commit_after><|endoftext|>


<commit_before><commit_msg>fix bad definitions of codes due to restrictive NEC tolerance<commit_after><|endoftext|>


<commit_before><commit_msg>expose fromHuman and toHuman<commit_after><|endoftext|>


<commit_before><commit_msg>use regex to replace newlines consitently<commit_after><|endoftext|>


<commit_before><commit_msg>[minor] If a function is supplied, call it with fittings as `this` value<commit_after><|endoftext|>


<commit_before><commit_msg>better error handling for apm install and for JSON parsing atom package.json<commit_after><|endoftext|>


<commit_before><commit_msg>Handle errors in update_changes_table()<commit_after><|endoftext|>


<commit_before>/*!
 * wordcount <https://github.com/jonschlinkert/wordcount>
 *
 * Copyright (c) 2014 Jon Schlinkert, contributors.
 * Licensed under the MIT License
 */

'use strict';

module.exports = function wordcount(str) {
  // CJK and Cyrillic regex from:
  // https://github.com/lepture/editor/blob/master/src/intro.js#L343
  // added cyrillic: \u0400-\u04FF - to the end of groups
  var pattern = /[a-zA-Z0-9_\u0392-\u03c9\u0400-\u04FF]+|[\u4E00-\u9FFF\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\uac00-\ud7af\u0400-\u04FF]+|\w+/g;

  var matches = str.match(pattern);
  if (matches === null) {
    return 0;
  }

  return matches.length;
};
<commit_msg>move regex out of function<commit_after>/*!
 * wordcount <https://github.com/jonschlinkert/wordcount>
 *
 * Copyright (c) 2014 Jon Schlinkert, contributors.
 * Licensed under the MIT License
 */

'use strict';

module.exports = function wordcount(str) {
  var matches = str.match(regex());
  return matches !== null
    ? matches.length
    : 0;
};

// Modified from: https://github.com/lepture/editor/blob/master/src/intro.js#L343
var regex = function() {
  return /[a-zA-Z0-9_\u0392-\u03c9\u0400-\u04FF]+|[\u4E00-\u9FFF\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\uac00-\ud7af\u0400-\u04FF]+|\w+/g;
};<|endoftext|>


<commit_before><commit_msg>defensive as fuck<commit_after><|endoftext|>


<commit_before>var castv2 = require('castv2-client')
var debug = require('debug')('chromecasts')
var events = require('events')
var mdns = require('multicast-dns')
var mime = require('mime')
var parseString = require('xml2js').parseString
var request = require('request')

var SSDP
try {
  SSDP = require('node-ssdp').Client
} catch (err) {
  SSDP = null
}

var thunky = require('thunky')
var url = require('url')

var noop = function () {}
var toMap = function (url) {
  return typeof url === 'string' ? {url: url} : url
}
var toSubtitles = function (url, i) {
  if (typeof url !== 'string') return url
  return {
    trackId: i + 1,
    type: 'TEXT',
    trackContentId: url,
    trackContentType: 'text/vtt',
    name: 'English',
    language: 'en-US',
    subtype: 'SUBTITLES'
  }
}

module.exports = function () {
  var dns = mdns()
  var that = new events.EventEmitter()
  var casts = {}
  var ssdp = SSDP ? new SSDP({logLevel: process.env.DEBUG ? 'trace' : false}) : null

  that.players = []

  var emit = function (cst) {
    if (!cst || !cst.host || cst.emitted) return
    cst.emitted = true

    var player = new events.EventEmitter()

    var connect = thunky(function reconnect (cb) {
      var client = new castv2.Client()

      client.on('error', function (err) {
        player.emit('error', err)
      })

      client.on('close', function () {
        connect = thunky(reconnect)
      })

      client.connect(player.host, function (err) {
        if (err) return cb(err)
        player.emit('connect')
        client.launch(castv2.DefaultMediaReceiver, function (err, p) {
          if (err) return cb(err)

          player.emit('ready')

          p.on('close', function () {
            connect = thunky(reconnect)
          })

          p.on('status', function (status) {
            player.emit('status', status)
          })

          cb(null, p)
        })
      })
    })

    var connectClient = thunky(function reconnectClient (cb) {
      var client = new castv2.Client()

      client.on('error', function () {
        connectClient = thunky(reconnectClient)
      })

      client.on('close', function () {
        connectClient = thunky(reconnectClient)
      })

      client.connect(player.host, function (err) {
        if (err) return cb(err)
        cb(null, client)
      })
    })

    player.name = cst.name
    player.host = cst.host

    player.client = function (cb) {
      connectClient(cb)
    }

    player.chromecastStatus = function (cb) {
      connectClient(function (err, client) {
        if (err) return cb(err)
        client.getStatus(cb)
      })
    }

    player.play = function (url, opts, cb) {
      if (typeof opts === 'function') return player.play(url, null, opts)
      if (!opts) opts = {}
      if (!url) return player.resume(cb)
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)

        var media = {
          contentId: url,
          contentType: opts.type || mime.lookup(url, 'video/mp4'),
          streamType: opts.streamType || 'BUFFERED',
          tracks: [].concat(opts.subtitles || []).map(toSubtitles),
          metadata: opts.metadata || {
            type: 0,
            metadataType: 0,
            title: opts.title || '',
            images: [].concat(opts.images || []).map(toMap)
          }
        }

        var autoSubtitles = opts.autoSubtitles
        if (autoSubtitles === false) autoSubtitles = 0
        if (autoSubtitles === true) autoSubtitles = 1

        var playerOptions = {
          autoplay: opts.autoPlay !== false,
          currentTime: opts.seek,
          activeTrackIds: opts.subtitles && (autoSubtitles === 0 ? [] : [autoSubtitles || 1])
        }

        p.load(media, playerOptions, cb)
      })
    }

    player.resume = function (cb) {
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)
        p.play()
      })
    }

    player.pause = function (cb) {
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)
        p.pause(cb)
      })
    }

    player.stop = function (cb) {
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)
        p.stop(cb)
      })
    }

    player.status = function (cb) {
      connect(function (err, p) {
        if (err) return cb(err)
        p.getStatus(cb)
      })
    }

    player.subtitles = function (id, cb) {
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)

        player.request({
          type: 'EDIT_TRACKS_INFO',
          activeTrackIds: id ? [id === true ? 1 : id] : []
        }, cb)
      })
    }

    player.volume = function (vol, cb) {
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)

        player.request({
          type: 'SET_VOLUME',
          volume: vol === 0 ? { muted: true } : { level: vol }
        }, cb)
      })
    }

    player.request = function (data, cb) {
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)
        p.media.sessionRequest(data, cb)
      })
    }

    player.seek = function (time, cb) {
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)
        p.seek(time, cb)
      })
    }

    that.players.push(player)
    that.emit('update', player)
  }

  dns.on('response', function (response) {
    response.answers.forEach(function (a) {
      if (a.type === 'PTR' && a.name === '_googlecast._tcp.local') {
        var name = a.data.replace('._googlecast._tcp.local', '')
        if (!casts[name]) casts[name] = {name: name, host: null}
      }
    })

    var onanswer = function (a) {
      debug('got answer %j', a)

      var name = a.name.replace('.local', '')
      if (a.type === 'A' && casts[name] && !casts[name].host) {
        casts[name].host = a.data
        emit(casts[name])
      }
    }

    response.additionals.forEach(onanswer)
    response.answers.forEach(onanswer)
  })

  if (ssdp) {
    ssdp.on('response', function (headers, statusCode, info) {
      if (!headers.LOCATION) return

      request.get(headers.LOCATION, function (err, res, body) {
        if (err) return

        parseString(body, {explicitArray: false, explicitRoot: false},
          function (err, service) {
            if (err) return
            if (!service.device) return
            if (service.device.manufacturer !== 'Google Inc.') return

            debug('device %j', service.device)

            var name = service.device.friendlyName

            if (!name) return

            var host = url.parse(service.URLBase).hostname

            if (!casts[name]) {
              casts[name] = {name: name, host: host}
              return emit(casts[name])
            }

            if (casts[name] && !casts[name].host) {
              casts[name].host = host
              emit(casts[name])
            }
          })
      })
    })
  }

  that.update = function () {
    debug('querying mdns and ssdp')
    if (ssdp) ssdp.search('urn:dial-multiscreen-org:device:dial:1')
    dns.query('_googlecast._tcp.local', 'PTR')
  }

  that.destroy = function () {
    dns.destroy()
  }

  that.update()

  return that
}
<commit_msg>Proper re-connection logic<commit_after>var castv2 = require('castv2-client')
var debug = require('debug')('chromecasts')
var events = require('events')
var mdns = require('multicast-dns')
var mime = require('mime')
var parseString = require('xml2js').parseString
var request = require('request')

var SSDP
try {
  SSDP = require('node-ssdp').Client
} catch (err) {
  SSDP = null
}

var thunky = require('thunky')
var url = require('url')

var noop = function () {}
var toMap = function (url) {
  return typeof url === 'string' ? {url: url} : url
}
var toSubtitles = function (url, i) {
  if (typeof url !== 'string') return url
  return {
    trackId: i + 1,
    type: 'TEXT',
    trackContentId: url,
    trackContentType: 'text/vtt',
    name: 'English',
    language: 'en-US',
    subtype: 'SUBTITLES'
  }
}

module.exports = function () {
  var dns = mdns()
  var that = new events.EventEmitter()
  var casts = {}
  var ssdp = SSDP ? new SSDP({logLevel: process.env.DEBUG ? 'trace' : false}) : null

  that.players = []

  var emit = function (cst) {
    if (!cst || !cst.host || cst.emitted) return
    cst.emitted = true

    var player = new events.EventEmitter()

    var connect = thunky(function reconnect (cb) {
      var client = new castv2.Client()

      client.on('error', function (err) {
        player.emit('error', err)
      })

      client.on('close', function () {
        connect = thunky(reconnect)
      })

      client.connect(player.host, function (err) {
        if (err) return cb(err)
        player.emit('connect')

        client.getSessions(function (err, sess) {
          if (err) return cb(err)

          var session = sess[0]
          if (session && session.appId === castv2.DefaultMediaReceiver.APP_ID) {
            client.join(session, castv2.DefaultMediaReceiver, ready)
          } else {
            client.launch(castv2.DefaultMediaReceiver, ready)
          }
        })

        function ready (err, p) {
          if (err) return cb(err)

          player.emit('ready')

          p.on('close', function () {
            connect = thunky(reconnect)
          })

          p.on('status', function (status) {
            player.emit('status', status)
          })

          cb(null, p)
        }
      })
    })

    var connectClient = thunky(function reconnectClient (cb) {
      var client = new castv2.Client()

      client.on('error', function () {
        connectClient = thunky(reconnectClient)
      })

      client.on('close', function () {
        connectClient = thunky(reconnectClient)
      })

      client.connect(player.host, function (err) {
        if (err) return cb(err)
        cb(null, client)
      })
    })

    player.name = cst.name
    player.host = cst.host

    player.client = function (cb) {
      connectClient(cb)
    }

    player.chromecastStatus = function (cb) {
      connectClient(function (err, client) {
        if (err) return cb(err)
        client.getStatus(cb)
      })
    }

    player.play = function (url, opts, cb) {
      if (typeof opts === 'function') return player.play(url, null, opts)
      if (!opts) opts = {}
      if (!url) return player.resume(cb)
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)

        var media = {
          contentId: url,
          contentType: opts.type || mime.lookup(url, 'video/mp4'),
          streamType: opts.streamType || 'BUFFERED',
          tracks: [].concat(opts.subtitles || []).map(toSubtitles),
          metadata: opts.metadata || {
            type: 0,
            metadataType: 0,
            title: opts.title || '',
            images: [].concat(opts.images || []).map(toMap)
          }
        }

        var autoSubtitles = opts.autoSubtitles
        if (autoSubtitles === false) autoSubtitles = 0
        if (autoSubtitles === true) autoSubtitles = 1

        var playerOptions = {
          autoplay: opts.autoPlay !== false,
          currentTime: opts.seek,
          activeTrackIds: opts.subtitles && (autoSubtitles === 0 ? [] : [autoSubtitles || 1])
        }

        p.load(media, playerOptions, cb)
      })
    }

    player.resume = function (cb) {
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)
        p.play()
      })
    }

    player.pause = function (cb) {
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)
        p.pause(cb)
      })
    }

    player.stop = function (cb) {
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)
        p.stop(cb)
      })
    }

    player.status = function (cb) {
      connect(function (err, p) {
        if (err) return cb(err)
        p.getStatus(cb)
      })
    }

    player.subtitles = function (id, cb) {
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)

        player.request({
          type: 'EDIT_TRACKS_INFO',
          activeTrackIds: id ? [id === true ? 1 : id] : []
        }, cb)
      })
    }

    player.volume = function (vol, cb) {
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)

        player.request({
          type: 'SET_VOLUME',
          volume: vol === 0 ? { muted: true } : { level: vol }
        }, cb)
      })
    }

    player.request = function (data, cb) {
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)
        p.media.sessionRequest(data, cb)
      })
    }

    player.seek = function (time, cb) {
      if (!cb) cb = noop
      connect(function (err, p) {
        if (err) return cb(err)
        p.seek(time, cb)
      })
    }

    that.players.push(player)
    that.emit('update', player)
  }

  dns.on('response', function (response) {
    response.answers.forEach(function (a) {
      if (a.type === 'PTR' && a.name === '_googlecast._tcp.local') {
        var name = a.data.replace('._googlecast._tcp.local', '')
        if (!casts[name]) casts[name] = {name: name, host: null}
      }
    })

    var onanswer = function (a) {
      debug('got answer %j', a)

      var name = a.name.replace('.local', '')
      if (a.type === 'A' && casts[name] && !casts[name].host) {
        casts[name].host = a.data
        emit(casts[name])
      }
    }

    response.additionals.forEach(onanswer)
    response.answers.forEach(onanswer)
  })

  if (ssdp) {
    ssdp.on('response', function (headers, statusCode, info) {
      if (!headers.LOCATION) return

      request.get(headers.LOCATION, function (err, res, body) {
        if (err) return

        parseString(body, {explicitArray: false, explicitRoot: false},
          function (err, service) {
            if (err) return
            if (!service.device) return
            if (service.device.manufacturer !== 'Google Inc.') return

            debug('device %j', service.device)

            var name = service.device.friendlyName

            if (!name) return

            var host = url.parse(service.URLBase).hostname

            if (!casts[name]) {
              casts[name] = {name: name, host: host}
              return emit(casts[name])
            }

            if (casts[name] && !casts[name].host) {
              casts[name].host = host
              emit(casts[name])
            }
          })
      })
    })
  }

  that.update = function () {
    debug('querying mdns and ssdp')
    if (ssdp) ssdp.search('urn:dial-multiscreen-org:device:dial:1')
    dns.query('_googlecast._tcp.local', 'PTR')
  }

  that.destroy = function () {
    dns.destroy()
  }

  that.update()

  return that
}
<|endoftext|>


<commit_before><commit_msg>replace tab to spaces<commit_after><|endoftext|>


<commit_before><commit_msg>Fixed error logging<commit_after><|endoftext|>


<commit_before><commit_msg>Added WhyRelavent in place of EligibilityDescription<commit_after><|endoftext|>


<commit_before><commit_msg>日志作为参数传进来，并且让connect的参数列表更明确<commit_after><|endoftext|>


<commit_before><commit_msg>overriding msgpack (experimental)<commit_after><|endoftext|>


<commit_before><commit_msg>Fix set and map issue<commit_after><|endoftext|>


<commit_before><commit_msg>removed console.log<commit_after><|endoftext|>


<commit_before><commit_msg>Added 'use strict', again<commit_after><|endoftext|>


<commit_before><commit_msg>whitelist the initializer as well<commit_after><|endoftext|>


<commit_before><commit_msg>Add the `inherit` function.<commit_after><|endoftext|>


<commit_before><commit_msg>Remove closed issues from issues.json<commit_after><|endoftext|>


<commit_before><commit_msg>upd: window width<commit_after><|endoftext|>


<commit_before><commit_msg>[api] Initial implementation.<commit_after><|endoftext|>


<commit_before><commit_msg>define and validate cli commands<commit_after><|endoftext|>


<commit_before><commit_msg>exception handling<commit_after><|endoftext|>


<commit_before><commit_msg>多了时间反馈<commit_after><|endoftext|>


<commit_before><commit_msg>Add source url to ical description<commit_after><|endoftext|>


<commit_before><commit_msg>set a version number, so is upgradable<commit_after><|endoftext|>


<commit_before><commit_msg>Correct typo on Express server route<commit_after><|endoftext|>


<commit_before><commit_msg>initial commit (separate mysql adapter from most data core)<commit_after><|endoftext|>


<commit_before><commit_msg>Add get user task type and param api<commit_after><|endoftext|>


<commit_before><commit_msg>Fix bad variable ref<commit_after><|endoftext|>


<commit_before><commit_msg>minor<commit_after><|endoftext|>


<commit_before><commit_msg>Replaced innerText with innerHTML<commit_after><|endoftext|>


<commit_before><commit_msg>Revert "[minor] allow render to be called sync and async"<commit_after><|endoftext|>


<commit_before><commit_msg>[AS] Move internalUser flag<commit_after><|endoftext|>


<commit_before><commit_msg>@mode in single quotes<commit_after><|endoftext|>


<commit_before><commit_msg>add history list<commit_after><|endoftext|>


<commit_before><commit_msg>Update readme<commit_after><|endoftext|>


<commit_before><commit_msg>Removed invalid call of `new` against Promise.resolve<commit_after><|endoftext|>


<commit_before><commit_msg>Maybe the callbacks have been missing?<commit_after><|endoftext|>


<commit_before><commit_msg>Remove freezeConstructorPropsConfigs, don't need it anymore<commit_after><|endoftext|>


<commit_before><commit_msg>Improve wrong parameter issues<commit_after><|endoftext|>


<commit_before><commit_msg>moved pixel processing to static methods<commit_after><|endoftext|>


<commit_before><commit_msg>added test route to test heatmap url heat<commit_after><|endoftext|>


<commit_before><commit_msg>go to the login page by default on startup<commit_after><|endoftext|>


<commit_before><commit_msg>'data' as global variable<commit_after><|endoftext|>


<commit_before><commit_msg>add nunchuck sample code<commit_after><|endoftext|>


<commit_before><commit_msg>Added Exec Function<commit_after><|endoftext|>


<commit_before><commit_msg>apply_default_style is not needed with an iframe<commit_after><|endoftext|>