<commit_before>var debug = require('debug')('metalsmith-beautify'),
    _ = require('lodash'),
    jsBeautify = require('js-beautify'),
    extname = require('path').extname,
    htmlBeautify = jsBeautify.html,
    cssBeautify = jsBeautify.css,

var TYPES = ['js', 'css', 'html'];

// Expose `plugin`
module.exports = plugin;

/**
 * Metalsmith plugin to beautify HTML/JS
 *
 * @param {Object} [options]
 * @return {Function}
 */
function plugin (options) {
    options = options || {};

    var defaults = {
        indent_size: 1,
        indent_char: "	",
        preserve_newlines: true,
        max_preserve_newlines: 1,
        brace_style: "collapse",
        keep_array_indentation: false,
        keep_function_indentation: false,
        space_before_conditional: true,
        break_chained_methods: false,
        eval_code: false,
        unescape_strings: false,
        wrap_line_length: 160
    };

    var dos = {}, compiledOptions = {};

    _.each(TYPES, function (type) {
        dos[type] = options[type] !== false;
        compiledOptions[type] = _.assign(defaults, options, options[type]);
    });

    return function (files, metalsmith, done){
        Object.keys(files).forEach(function(file){
            debug('checking file: %s', file);
            var data = files[file],
                formatted;

            if (dos.html && isHtml(file)) {
                debug('processing file as html: %s', file);
                formatted = htmlBeautify(data.contents.toString(), compiledOptions.html);
            } else if (dos.js && isJs(file)) {
                debug('processing file as js: %s', file);
                formatted = jsBeautify(data.contents.toString(), compiledOptions.js);
            } else if (dos.css && isCss(file)) {
                debug('processing file as css: %s', file);
                formatted = cssBeautify(data.contents.toString(), compiledOptions.css);
            } else if (dos.html && isMarkdown(file)) {
                console.warn('Found Markdown file when trying to beautify HTML, you should run this plugin after the markdown plugin.');
            }

            if (formatted) {
                data.contents = new Buffer(formatted);
            }
        });
        done();
    };
}

/**
 * Check if a `file` is the specified type
 *
 * @param {String} file
 * @return {Boolean}
 */
function isMarkdown (file) {
    return /\.md|\.markdown/.test(extname(file));
}
function isJs (file) {
    return /\.js/.test(extname(file));
}
function isHtml (file) {
    return /\.html|\.htm/.test(extname(file));
}
function isCss (file) {
    return /\.css/.test(extname(file));
}
<commit_msg>Replace multiple whitespaces<commit_after>var debug = require('debug')('metalsmith-beautify'),
    _ = require('lodash'),
    jsBeautify = require('js-beautify'),
    extname = require('path').extname,
    htmlBeautify = jsBeautify.html,
    cssBeautify = jsBeautify.css,

var TYPES = ['js', 'css', 'html'];

// Expose `plugin`
module.exports = plugin;

/**
 * Metalsmith plugin to beautify HTML/JS
 *
 * @param {Object} [options]
 * @return {Function}
 */
function plugin (options) {
    options = options || {};

    var defaults = {
        indent_size: 1,
        indent_char: "	",
        preserve_newlines: true,
        max_preserve_newlines: 1,
        brace_style: "collapse",
        keep_array_indentation: false,
        keep_function_indentation: false,
        space_before_conditional: true,
        break_chained_methods: false,
        eval_code: false,
        unescape_strings: false,
        wrap_line_length: 160
    };

    var dos = {}, compiledOptions = {};

    _.each(TYPES, function (type) {
        dos[type] = options[type] !== false;
        compiledOptions[type] = _.assign(defaults, options, options[type]);
    });

    return function (files, metalsmith, done){
        Object.keys(files).forEach(function(file){
            debug('checking file: %s', file);
            var data = files[file],
                formatted;

            if (dos.html && isHtml(file)) {
                debug('processing file as html: %s', file);
                var contents = data.contents.toString().replace(/\s+/gm, " ");
                formatted = htmlBeautify(contents, compiledOptions.html);
            } else if (dos.js && isJs(file)) {
                debug('processing file as js: %s', file);
                formatted = jsBeautify(data.contents.toString(), compiledOptions.js);
            } else if (dos.css && isCss(file)) {
                debug('processing file as css: %s', file);
                formatted = cssBeautify(data.contents.toString(), compiledOptions.css);
            } else if (dos.html && isMarkdown(file)) {
                console.warn('Found Markdown file when trying to beautify HTML, you should run this plugin after the markdown plugin.');
            }

            if (formatted) {
                data.contents = new Buffer(formatted);
            }
        });
        done();
    };
}

/**
 * Check if a `file` is the specified type
 *
 * @param {String} file
 * @return {Boolean}
 */
function isMarkdown (file) {
    return /\.md|\.markdown/.test(extname(file));
}
function isJs (file) {
    return /\.js/.test(extname(file));
}
function isHtml (file) {
    return /\.html|\.htm/.test(extname(file));
}
function isCss (file) {
    return /\.css/.test(extname(file));
}
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import c from 'classnames';
import FlatButton from 'material-ui/FlatButton';
import Dialog from 'material-ui/Dialog';
import {Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn} from 'material-ui/Table';
import {range} from 'ramda';
import {Link} from 'react-router';
import {get} from 'lodash';
import {hashHistory} from 'react-router';

import TopBar from './../../components/TopBar/TopBar';
import Breadcrumb from './../Breadcrumb/Breadcrumb';
import {setSnackBarParams} from './../../redux/modules/ui';
import {getFolder} from './../../redux/modules/folder';
import {listFolderEntries, setSelectedFolderEntryIds, clearSelectedFolderEntryIds,
  deleteEntries} from './../../redux/modules/entry';
import {setCachePageEntries} from './../../redux/modules/cache';

import injectF from './../../helpers/injectF';
import injectPush from './../../helpers/injectPush';
import resolve from './../../helpers/resolve';
import Pagination from './../../components/Pagination/Pagination';
import SearchBar from './../../components/SearchBar/SearchBar';
import hasValue from './../../helpers/hasValue';
import getFontSize from './../../helpers/getFontSize';
import SEARCH_TYPES from './../../constants/searchTypes';

const styles = require('./PageEntries.scss');

const SORT_METHODS = ['', 'asc', 'desc'];
const SORT_CLASSNAME_MAP = {
  ['']: 'fa fa-sort',
  asc: 'fa fa-sort-asc',
  desc: 'fa fa-sort-desc'
};

// how to access params outside of react component
// https://github.com/reactjs/react-router-redux#how-do-i-access-router-state-in-a-container-component
@connect(({main, folder, entry, cache, routing}, {params}) => {
  const folderId = parseInt(params.id, 10);
  return {
    cache: get(cache.get('cachePageEntriesDataSet'), folderId, {}),
    perpage: entry.get('perpage'),
    folder: folder.get('folder'),
    importingFolderId: folder.get('importingFolderId'),
    interfaceFontSizeScalingFactor: main.get('interfaceFontSizeScalingFactor'),
    folderEntries: entry.get('folderEntries'),
    folderEntryCount: entry.get('folderEntryCount'),
    selectedFolderEntryMap: entry.get('selectedFolderEntryMap')
  };
}, {listFolderEntries, setSnackBarParams, setSelectedFolderEntryIds, setCachePageEntries,
  deleteEntries, clearSelectedFolderEntryIds})
@injectPush
@injectF
@resolve(({dispatch}, {params, page, perpage, cache}) => {

  const promises = [];
  promises.push(dispatch(getFolder({id: params.id})));
  promises.push(dispatch(setSelectedFolderEntryIds([])));

  const folderId = params.id;

  const searchParams = Object.assign({
    folderId,
    page,
    perpage,
    pageNumSortMethod: ''
  }, cache);
  promises.push(dispatch(listFolderEntries(searchParams)));

  return Promise.all(promises);
})
export default class PageEntries extends Component {

  static propTypes = {
    cache: PropTypes.object.isRequired,
    push: PropTypes.func.isRequired,
    perpage: PropTypes.number.isRequired,
    f: PropTypes.func.isRequired,
    folder: PropTypes.object.isRequired,
    folderEntries: PropTypes.array.isRequired,
    interfaceFontSizeScalingFactor: PropTypes.number.isRequired,
    folderEntryCount: PropTypes.number.isRequired,
    params: PropTypes.object.isRequired,
    listFolderEntries: PropTypes.func.isRequired,
    setCachePageEntries: PropTypes.func.isRequired,
    setSelectedFolderEntryIds: PropTypes.func.isRequired,
    clearSelectedFolderEntryIds: PropTypes.func.isRequired,
    selectedFolderEntryMap: PropTypes.object.isRequired,
    deleteEntries: PropTypes.func.isRequired,
    importingFolderId: PropTypes.number,
    setSnackBarParams: PropTypes.func.isRequired
  };

  constructor(props) {
    super(props);
    const [pageNumSortMethod] = SORT_METHODS;
    this.state = Object.assign({
      page: 1,
      tableKey: 0,
      searchKeyword: '',
      searchType: 'source-entry',
      pageNumSortMethod,
      isConfirmEntryDeletionOpen: false
    }, props.cache);
  }

  componentWillUpdate(nextProps, nextState) {
    const {page, searchKeyword, searchType, pageNumSortMethod} = this.state;
    const {perpage, listFolderEntries, setCachePageEntries} = this.props;
    const nextPage = nextState.page;
    const nextPerpage = nextProps.perpage;
    const nextSearchKeyword = nextState.searchKeyword;
    const nextSearchType = nextState.searchType;
    const folderId = nextProps.folder.id;
    const nextPageNumSortMethod = nextState.pageNumSortMethod;

    if ((page !== nextPage) || (perpage !== nextPerpage) ||
        (searchKeyword !== nextSearchKeyword) || (searchType !== nextSearchType) || (pageNumSortMethod !== nextPageNumSortMethod)) {

      setCachePageEntries(folderId, {
        page: nextPage,
        searchKeyword: nextSearchKeyword,
        searchType: nextSearchType,
        pageNumSortMethod: nextPageNumSortMethod
      });

      listFolderEntries({
        folderId,
        page: nextPage,
        perpage: nextPerpage,
        searchKeyword: nextSearchKeyword.trim(),
        searchType: nextSearchType,
        pageNumSortMethod: nextPageNumSortMethod
      });
    }
  }

  handleRowSelection = (res) => {

    const {setSelectedFolderEntryIds, perpage, folderEntries} = this.props;

    let indices = res;

    if ('all' === res) {
      indices = range(0, perpage);
    }
    else if ('none' === res) {
      indices = [];
    }

    const ids = folderEntries.filter((row, index) => (-1 !== indices.indexOf(index)))
      .map((row) => row.id);

    setSelectedFolderEntryIds(ids);
  };

  goToSingleFolderEntryPage = (entryId) => {
    const {folder, push} = this.props;
    return () => push(`/folders/${folder.id}/entries/${entryId}`);
  };

  changePageNumberSortMethod = () => {
    const {pageNumSortMethod} = this.state;
    const index = SORT_METHODS.indexOf(pageNumSortMethod);
    const nextIndex = (index + 1) % 3;
    this.setState({pageNumSortMethod: SORT_METHODS[nextIndex]});
  };

  renderFolderEntries() {

    const fontSize = 20;
    const colStyle = {fontSize, width: 'initial'};
    const {tableKey, pageNumSortMethod} = this.state;
    const {folderEntries, f} = this.props;

    const tableRows = folderEntries.map((entry) => {
      return (
        <TableRow key={`table-row-${entry.id}`}>
          <TableRowColumn style={colStyle}>
            {entry.sourceEntry}
          </TableRowColumn>
          <TableRowColumn style={colStyle}>{get(entry, 'pageNum', '')}</TableRowColumn>
        </TableRow>
      );
    });

    // https://github.com/callemall/material-ui/issues/6006#issuecomment-277669719
    const key = `table-workaround-${tableKey}`;

    return (

        <TableHeader>
          <TableRow>
            <TableHeaderColumn style={colStyle}>{f('source-entry')}</TableHeaderColumn>
            <TableHeaderColumn style={colStyle}>
              {f('page-num')}
              <button className={styles.btnSort} onClick={this.changePageNumberSortMethod}>

              </button>
            </TableHeaderColumn>
          </TableRow>
        </TableHeader>
        <TableBody showRowHover deselectOnClickaway={false}>{tableRows}</TableBody>

    );
  }

  goToAddFolderEntryPage = () => {
    const {folder, push} = this.props;
    push(`/folders/${folder.id}/entries/add`);
  };

  updateTableKey = () => this.setState((prevState) => ({tableKey: prevState.tableKey + 1}));

  deleteSelectedFolderEntries = async () => {

    const {page, pageNumSortMethod} = this.state;
    const {selectedFolderEntryMap, f, deleteEntries, perpage, params, listFolderEntries,
      setSnackBarParams, folderEntryCount, clearSelectedFolderEntryIds} = this.props;

    const ids = Object.keys(selectedFolderEntryMap);
    await deleteEntries({ids});
    clearSelectedFolderEntryIds();
    const totalPages = Math.ceil((folderEntryCount - ids.length) / perpage);
    const nextPage = (page > totalPages) ? totalPages : page;

    await listFolderEntries({folderId: params.id, page: nextPage, perpage, pageNumSortMethod});
    this.setState({page: nextPage});
    setSnackBarParams(true, f('folder-entries-has-been-deleted', {count: ids.length}));
    this.updateTableKey();
    this.closeConfirmEntryDeletionDialog();
  };

  renderDeleteButton() {
    const {selectedFolderEntryMap, f, interfaceFontSizeScalingFactor} = this.props;
    if (Object.keys(selectedFolderEntryMap).length > 0) {
      const buttonFontSize = getFontSize(interfaceFontSizeScalingFactor, 0.9);
      return (
        <FlatButton label={f('delete')} labelStyle={{fontSize: buttonFontSize}}
          icon={} onTouchTap={this.openConfirmEntryDeletionDialog} />
      );
    }
  }

  handlePageButtonTouchTap = (page) => {
    this.updateTableKey();
    this.setState({page});
  };

  handleSearchInputChange = (searchKeyword) => {
    if (this.state.searchKeyword !== searchKeyword) {
      this.setState({page: 1});
    }
    this.setState({searchKeyword});
  };

  handleSearchTypeChange = (event, key, searchType) => this.setState({searchType});

  getMatchedCount() {

    const {searchKeyword} = this.state;
    const {folderEntryCount} = this.props;

    if (hasValue(searchKeyword)) {
      return folderEntryCount;
    }
    return 0;
  }

  handleClearSearchButtonTouchTap = () => {
    this.setState({searchKeyword: ''});
    this.refs.searchBar.getWrappedInstance().getWrappedInstance().clear();
  };

  isImporting() {
    const {folder, importingFolderId} = this.props;
    return folder.id === importingFolderId;
  }

  openConfirmEntryDeletionDialog = () => this.setState({isConfirmEntryDeletionOpen: true});

  closeConfirmEntryDeletionDialog = () => this.setState({isConfirmEntryDeletionOpen: false});

  renderConfirmEntryDeletionDialog() {
    const {isConfirmEntryDeletionOpen} = this.state;
    const {f, interfaceFontSizeScalingFactor} = this.props;
    const dialogContentFontSize = getFontSize(interfaceFontSizeScalingFactor, 1.2);
    const dialogContentLineHeight = getFontSize(interfaceFontSizeScalingFactor, 1.2);
    const pStyle = {
      fontSize: dialogContentFontSize,
      lineHeight: dialogContentLineHeight
    };
    const actions = [
      <FlatButton label={f('cancel')} primary onTouchTap={this.closeConfirmEntryDeletionDialog} />,
      <FlatButton label={f('delete')} primary keyboardFocused onTouchTap={this.deleteSelectedFolderEntries} />
    ];
    return (
      <Dialog title={f('title-confirm-delete-dialog')} actions={actions}
        modal={false} open={isConfirmEntryDeletionOpen} onRequestClose={this.closeConfirmEntryDeletionDialog}>

{f('content-confirm-delete-dialog1')}

{f('content-confirm-delete-dialog2')}

      </Dialog>
    );
  }

  render() {

    const {page, searchType, searchKeyword} = this.state;
    const {f, folder, folderEntryCount, perpage, interfaceFontSizeScalingFactor} = this.props;
    const matchedCount = this.getMatchedCount();
    const buttonFontSize = getFontSize(interfaceFontSizeScalingFactor, 0.9);

    return (

        <TopBar>
          <Breadcrumb>
            <Link to="/">{f('folders')}</Link>
            {f('folder-entries', {folderName: folder.name})}
          </Breadcrumb>

            {this.renderDeleteButton()}
            <FlatButton icon={} labelStyle={{fontSize: buttonFontSize}}
              label={f('add-entry')} onTouchTap={this.goToAddFolderEntryPage} disabled={this.isImporting()} />
            <FlatButton icon={} labelStyle={{fontSize: buttonFontSize}}
              label={f('back')} onTouchTap={hashHistory.goBack} />

        </TopBar>

          <SearchBar ref="searchBar" onInputChange={this.handleSearchInputChange} searchTypes={SEARCH_TYPES}
            searchKeyword={searchKeyword} matchedCount={matchedCount}
            selectedSearchType={searchType} onSearchTypeChange={this.handleSearchTypeChange}
            onClearFilterButtonTouchTap={this.handleClearSearchButtonTouchTap} />
          {this.renderFolderEntries()}
          {(folderEntryCount > perpage) && <Pagination current={page} total={Math.ceil(folderEntryCount / perpage)}
            onButtonTouchTap={this.handlePageButtonTouchTap} />}

        {this.renderConfirmEntryDeletionDialog()}

    );
  }
}
<commit_msg>re-style<commit_after>import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import c from 'classnames';
import FlatButton from 'material-ui/FlatButton';
import Dialog from 'material-ui/Dialog';
import {Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn} from 'material-ui/Table';
import {range} from 'ramda';
import {Link} from 'react-router';
import {get} from 'lodash';
import {hashHistory} from 'react-router';

import TopBar from './../../components/TopBar/TopBar';
import Breadcrumb from './../Breadcrumb/Breadcrumb';
import {setSnackBarParams} from './../../redux/modules/ui';
import {getFolder} from './../../redux/modules/folder';
import {listFolderEntries, setSelectedFolderEntryIds, clearSelectedFolderEntryIds,
  deleteEntries} from './../../redux/modules/entry';
import {setCachePageEntries} from './../../redux/modules/cache';

import injectF from './../../helpers/injectF';
import injectPush from './../../helpers/injectPush';
import resolve from './../../helpers/resolve';
import Pagination from './../../components/Pagination/Pagination';
import SearchBar from './../../components/SearchBar/SearchBar';
import hasValue from './../../helpers/hasValue';
import getFontSize from './../../helpers/getFontSize';
import SEARCH_TYPES from './../../constants/searchTypes';

const styles = require('./PageEntries.scss');

const SORT_METHODS = ['', 'asc', 'desc'];
const SORT_CLASSNAME_MAP = {
  ['']: 'fa fa-sort',
  asc: 'fa fa-sort-asc',
  desc: 'fa fa-sort-desc'
};

// how to access params outside of react component
// https://github.com/reactjs/react-router-redux#how-do-i-access-router-state-in-a-container-component
@connect(({main, folder, entry, cache, routing}, {params}) => {
  const folderId = parseInt(params.id, 10);
  return {
    cache: get(cache.get('cachePageEntriesDataSet'), folderId, {}),
    perpage: entry.get('perpage'),
    folder: folder.get('folder'),
    importingFolderId: folder.get('importingFolderId'),
    interfaceFontSizeScalingFactor: main.get('interfaceFontSizeScalingFactor'),
    folderEntries: entry.get('folderEntries'),
    folderEntryCount: entry.get('folderEntryCount'),
    selectedFolderEntryMap: entry.get('selectedFolderEntryMap')
  };
}, {listFolderEntries, setSnackBarParams, setSelectedFolderEntryIds, setCachePageEntries,
  deleteEntries, clearSelectedFolderEntryIds})
@injectPush
@injectF
@resolve(({dispatch}, {params, page, perpage, cache}) => {

  const promises = [];
  const folderId = params.id;

  promises.push(dispatch(getFolder({id: folderId})));
  promises.push(dispatch(setSelectedFolderEntryIds([])));

  const searchParams = Object.assign({
    folderId,
    page,
    perpage,
    pageNumSortMethod: ''
  }, cache);
  promises.push(dispatch(listFolderEntries(searchParams)));

  return Promise.all(promises);
})
export default class PageEntries extends Component {

  static propTypes = {
    cache: PropTypes.object.isRequired,
    push: PropTypes.func.isRequired,
    perpage: PropTypes.number.isRequired,
    f: PropTypes.func.isRequired,
    folder: PropTypes.object.isRequired,
    folderEntries: PropTypes.array.isRequired,
    interfaceFontSizeScalingFactor: PropTypes.number.isRequired,
    folderEntryCount: PropTypes.number.isRequired,
    params: PropTypes.object.isRequired,
    listFolderEntries: PropTypes.func.isRequired,
    setCachePageEntries: PropTypes.func.isRequired,
    setSelectedFolderEntryIds: PropTypes.func.isRequired,
    clearSelectedFolderEntryIds: PropTypes.func.isRequired,
    selectedFolderEntryMap: PropTypes.object.isRequired,
    deleteEntries: PropTypes.func.isRequired,
    importingFolderId: PropTypes.number,
    setSnackBarParams: PropTypes.func.isRequired
  };

  constructor(props) {
    super(props);
    const [pageNumSortMethod] = SORT_METHODS;
    this.state = Object.assign({
      page: 1,
      tableKey: 0,
      searchKeyword: '',
      searchType: 'source-entry',
      pageNumSortMethod,
      isConfirmEntryDeletionOpen: false
    }, props.cache);
  }

  componentWillUpdate(nextProps, nextState) {
    const {page, searchKeyword, searchType, pageNumSortMethod} = this.state;
    const {perpage, listFolderEntries, setCachePageEntries} = this.props;
    const nextPage = nextState.page;
    const nextPerpage = nextProps.perpage;
    const nextSearchKeyword = nextState.searchKeyword;
    const nextSearchType = nextState.searchType;
    const folderId = nextProps.folder.id;
    const nextPageNumSortMethod = nextState.pageNumSortMethod;

    if ((page !== nextPage) || (perpage !== nextPerpage) ||
        (searchKeyword !== nextSearchKeyword) || (searchType !== nextSearchType) || (pageNumSortMethod !== nextPageNumSortMethod)) {

      setCachePageEntries(folderId, {
        page: nextPage,
        searchKeyword: nextSearchKeyword,
        searchType: nextSearchType,
        pageNumSortMethod: nextPageNumSortMethod
      });

      listFolderEntries({
        folderId,
        page: nextPage,
        perpage: nextPerpage,
        searchKeyword: nextSearchKeyword.trim(),
        searchType: nextSearchType,
        pageNumSortMethod: nextPageNumSortMethod
      });
    }
  }

  handleRowSelection = (res) => {

    const {setSelectedFolderEntryIds, perpage, folderEntries} = this.props;

    let indices = res;

    if ('all' === res) {
      indices = range(0, perpage);
    }
    else if ('none' === res) {
      indices = [];
    }

    const ids = folderEntries.filter((row, index) => (-1 !== indices.indexOf(index)))
      .map((row) => row.id);

    setSelectedFolderEntryIds(ids);
  };

  goToSingleFolderEntryPage = (entryId) => {
    const {folder, push} = this.props;
    return () => push(`/folders/${folder.id}/entries/${entryId}`);
  };

  changePageNumberSortMethod = () => {
    const {pageNumSortMethod} = this.state;
    const index = SORT_METHODS.indexOf(pageNumSortMethod);
    const nextIndex = (index + 1) % 3;
    this.setState({pageNumSortMethod: SORT_METHODS[nextIndex]});
  };

  renderFolderEntries() {

    const fontSize = 20;
    const colStyle = {fontSize, width: 'initial'};
    const {tableKey, pageNumSortMethod} = this.state;
    const {folderEntries, f} = this.props;

    const tableRows = folderEntries.map((entry) => {
      return (
        <TableRow key={`table-row-${entry.id}`}>
          <TableRowColumn style={colStyle}>
            {entry.sourceEntry}
          </TableRowColumn>
          <TableRowColumn style={colStyle}>{get(entry, 'pageNum', '')}</TableRowColumn>
        </TableRow>
      );
    });

    // https://github.com/callemall/material-ui/issues/6006#issuecomment-277669719
    const key = `table-workaround-${tableKey}`;

    return (

        <TableHeader>
          <TableRow>
            <TableHeaderColumn style={colStyle}>{f('source-entry')}</TableHeaderColumn>
            <TableHeaderColumn style={colStyle}>
              {f('page-num')}
              <button className={styles.btnSort} onClick={this.changePageNumberSortMethod}>

              </button>
            </TableHeaderColumn>
          </TableRow>
        </TableHeader>
        <TableBody showRowHover deselectOnClickaway={false}>{tableRows}</TableBody>

    );
  }

  goToAddFolderEntryPage = () => {
    const {folder, push} = this.props;
    push(`/folders/${folder.id}/entries/add`);
  };

  updateTableKey = () => this.setState((prevState) => ({tableKey: prevState.tableKey + 1}));

  deleteSelectedFolderEntries = async () => {

    const {page, pageNumSortMethod} = this.state;
    const {selectedFolderEntryMap, f, deleteEntries, perpage, params, listFolderEntries,
      setSnackBarParams, folderEntryCount, clearSelectedFolderEntryIds} = this.props;

    const ids = Object.keys(selectedFolderEntryMap);
    await deleteEntries({ids});
    clearSelectedFolderEntryIds();
    const totalPages = Math.ceil((folderEntryCount - ids.length) / perpage);
    const nextPage = (page > totalPages) ? totalPages : page;

    await listFolderEntries({folderId: params.id, page: nextPage, perpage, pageNumSortMethod});
    this.setState({page: nextPage});
    setSnackBarParams(true, f('folder-entries-has-been-deleted', {count: ids.length}));
    this.updateTableKey();
    this.closeConfirmEntryDeletionDialog();
  };

  renderDeleteButton() {
    const {selectedFolderEntryMap, f, interfaceFontSizeScalingFactor} = this.props;
    if (Object.keys(selectedFolderEntryMap).length > 0) {
      const buttonFontSize = getFontSize(interfaceFontSizeScalingFactor, 0.9);
      return (
        <FlatButton label={f('delete')} labelStyle={{fontSize: buttonFontSize}}
          icon={} onTouchTap={this.openConfirmEntryDeletionDialog} />
      );
    }
  }

  handlePageButtonTouchTap = (page) => {
    this.updateTableKey();
    this.setState({page});
  };

  handleSearchInputChange = (searchKeyword) => {
    if (this.state.searchKeyword !== searchKeyword) {
      this.setState({page: 1});
    }
    this.setState({searchKeyword});
  };

  handleSearchTypeChange = (event, key, searchType) => this.setState({searchType});

  getMatchedCount() {

    const {searchKeyword} = this.state;
    const {folderEntryCount} = this.props;

    if (hasValue(searchKeyword)) {
      return folderEntryCount;
    }
    return 0;
  }

  handleClearSearchButtonTouchTap = () => {
    this.setState({searchKeyword: ''});
    this.refs.searchBar.getWrappedInstance().getWrappedInstance().clear();
  };

  isImporting() {
    const {folder, importingFolderId} = this.props;
    return folder.id === importingFolderId;
  }

  openConfirmEntryDeletionDialog = () => this.setState({isConfirmEntryDeletionOpen: true});

  closeConfirmEntryDeletionDialog = () => this.setState({isConfirmEntryDeletionOpen: false});

  renderConfirmEntryDeletionDialog() {
    const {isConfirmEntryDeletionOpen} = this.state;
    const {f, interfaceFontSizeScalingFactor} = this.props;
    const dialogContentFontSize = getFontSize(interfaceFontSizeScalingFactor, 1.2);
    const dialogContentLineHeight = getFontSize(interfaceFontSizeScalingFactor, 1.2);
    const pStyle = {
      fontSize: dialogContentFontSize,
      lineHeight: dialogContentLineHeight
    };
    const actions = [
      <FlatButton label={f('cancel')} primary onTouchTap={this.closeConfirmEntryDeletionDialog} />,
      <FlatButton label={f('delete')} primary keyboardFocused onTouchTap={this.deleteSelectedFolderEntries} />
    ];
    return (
      <Dialog title={f('title-confirm-delete-dialog')} actions={actions}
        modal={false} open={isConfirmEntryDeletionOpen} onRequestClose={this.closeConfirmEntryDeletionDialog}>

{f('content-confirm-delete-dialog1')}

{f('content-confirm-delete-dialog2')}

      </Dialog>
    );
  }

  render() {

    const {page, searchType, searchKeyword} = this.state;
    const {f, folder, folderEntryCount, perpage, interfaceFontSizeScalingFactor} = this.props;
    const matchedCount = this.getMatchedCount();
    const buttonFontSize = getFontSize(interfaceFontSizeScalingFactor, 0.9);

    return (

        <TopBar>
          <Breadcrumb>
            <Link to="/">{f('folders')}</Link>
            {f('folder-entries', {folderName: folder.name})}
          </Breadcrumb>

            {this.renderDeleteButton()}
            <FlatButton icon={} labelStyle={{fontSize: buttonFontSize}}
              label={f('add-entry')} onTouchTap={this.goToAddFolderEntryPage} disabled={this.isImporting()} />
            <FlatButton icon={} labelStyle={{fontSize: buttonFontSize}}
              label={f('back')} onTouchTap={hashHistory.goBack} />

        </TopBar>

          <SearchBar ref="searchBar" onInputChange={this.handleSearchInputChange} searchTypes={SEARCH_TYPES}
            searchKeyword={searchKeyword} matchedCount={matchedCount}
            selectedSearchType={searchType} onSearchTypeChange={this.handleSearchTypeChange}
            onClearFilterButtonTouchTap={this.handleClearSearchButtonTouchTap} />
          {this.renderFolderEntries()}
          {(folderEntryCount > perpage) && <Pagination current={page} total={Math.ceil(folderEntryCount / perpage)}
            onButtonTouchTap={this.handlePageButtonTouchTap} />}

        {this.renderConfirmEntryDeletionDialog()}

    );
  }
}
<|endoftext|>


<commit_before>'use strict';

/**
 * Dependencies
 */
var koa = require('koa')
  , views = require('co-views')
  , route = require('koa-route')
  , responseTime = require('koa-response-time')
  , ratelimit = require('koa-ratelimit')
  , compress = require('koa-compress')
  , logger = require('koa-logger')
  , serve = require('koa-static')
  , path = require('path')
  , redis = require('redis');

/**
 * Application
 */
module.exports = function(config) {
  config = config || {};

  var app = koa()
    , redisClient;

  /**
   * Redis client
   */
  if (config.options.redis) {
    redisClient = redis.createClient(config.options.redis.port, config.options.redis.host, config.options.redis.options);
  }

  /**
   * X-Response-Time
   */
  app.use(responseTime());

  /**
   * Logger
   */
  app.use(logger());

  /**
   * Compression
   */
  app.use(compress());

  /**
   * Limiting rate
   */
  if (config.options.ratelimit && redisClient) {
    app.use(ratelimit({
      max: config.options.ratelimit.limit,
      duration: config.options.ratelimit.duration,
      db: redisClient
    }));
  }

  /**
   * Public folder
   */
  app.use(serve(path.join(process.cwd(), 'public')));

  /**
   * Routes
   */
  var routes = config.routes;
  for (var urlPath in routes) {
    app.use(route.get(urlPath, routes[urlPath]));
  }

  /**
   * View renderer
   */
  app.render = views(path.join(process.cwd(), 'views'), { ext: 'ejs' });

  return app;
}
<commit_msg>Main application bootstrapping modified.<commit_after>'use strict';

/**
 * Dependencies
 */
var koa = require('koa')
  , views = require('co-views')
  , route = require('koa-route')
  , responseTime = require('koa-response-time')
  , ratelimit = require('koa-ratelimit')
  , compress = require('koa-compress')
  , logger = require('koa-logger')
  , serve = require('koa-static')
  , path = require('path')
  , redis = require('redis');

/**
 * Application
 */
module.exports = function(config) {
  config = config || {};

  var app = koa()
    , dispatch = require('./dispatcher').dispatch
    , redisClient;

  /**
   * Redis client
   */
  if (config.options.redis) {
    redisClient = redis.createClient(config.options.redis.port, config.options.redis.host, config.options.redis.options);
  }

  /**
   * X-Response-Time
   */
  app.use(responseTime());

  /**
   * Logger
   */
  app.use(logger());

  /**
   * Compression
   */
  app.use(compress());

  /**
   * Limiting rate
   */
  if (config.options.ratelimit && redisClient) {
    app.use(ratelimit({
      max: config.options.ratelimit.limit,
      duration: config.options.ratelimit.duration,
      db: redisClient
    }));
  }

  /**
   * Public folder
   */
  app.use(serve(path.join(process.cwd(), 'public')));

  /**
   * Routes
   * @todo provide routing for different types of HTTP-requests
   */
  var routes = config.routes;
  for (var urlPath in routes) {
    app.use(route.get(urlPath, dispatch(routes[urlPath])));
  }

  /**
   * View renderer
   */
  app.render = views(path.join(process.cwd(), 'views'), { ext: 'ejs' });

  return app;
}
<|endoftext|>


<commit_before>'use strict';

/**
 * @file Helper for libreOffice file processing
 * For more information about libreoffice :
 * http://www.libreoffice.org/
 */

var fs = require('fs');
var async = require('async');
var shellExec = require('child_process').exec;
var nativePath = require('path');
var request = require('supertest');
var async = require('async');
var config = require('../config/configuration.js');

var HydrationError = require('anyfetch-hydrater').HydrationError;

var extensions = {
  '.xls': 'calc_pdf_Export',
  '.xlsx': 'calc_pdf_Export',
  '.ods': 'calc_pdf_Export',
  '.doc': 'writer_pdf_Export',
  '.docx': 'writer_pdf_Export',
  '.odt': 'writer_pdf_Export',
  '.rtf': 'writer_pdf_Export',
  '.ppt': 'impress_pdf_Export',
  '.pptx': 'impress_pdf_Export',
  '.odp': 'impress_pdf_Export',
  '.pps': 'impress_pdf_Export',
  '.ppsx': 'impress_pdf_Export',
  '.key': 'impress_pdf_Export',
};

var contentType = {
  "application/msword": 'writer_pdf_Export',
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document": 'writer_pdf_Export',
  "application/vnd.ms-excel": 'calc_pdf_Export',
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": 'calc_pdf_Export',
  "application/vnd.ms-powerpoint": 'impress_pdf_Export',
  "application/vnd.openxmlformats-officedocument.presentationml.presentation": 'impress_pdf_Export',
  "application/vnd.openxmlformats-officedocument.presentationml.slideshow": 'impress_pdf_Export'
};

/**
 * Extract the content in html of the specified document
 *
 * @param {string} path Path of the specified file
 * @param {string} document to hydrate
 * @param {function} cb Callback, first parameter, is the error if any, then the processed data
 */
module.exports = function(path, document, changes, cb) {
  var finalCb = cb;
  var pdfFile;
  async.waterfall([
    function getConverter(cb) {
      var converter;

      if(document.metadata && document.metadata.path) {
        var file = nativePath.basename(document.metadata.path);
        var fileExtension = nativePath.extname(file).toLowerCase();
        if(extensions[fileExtension]) {
          converter = extensions[fileExtension];
        }
      }
      if(document.data && document.data.content_type) {
        if(contentType[document.data.content_type]) {
          converter = contentType[document.data.content_type];
        }
      }

      if(!converter) {
        return cb(new HydrationError("Unknown file extension or content type"));
      }
      else {
        cb(null, converter);
      }
    },
    function convertToPdf(converter, cb) {
      var options = [
        '--headless',
        '--norestore',
        '--nolockcheck'
      ];

      shellExec('loffice ' + options.join(' ') + ' --convert-to pdf:"' + converter + '" --outdir /tmp ' + path, {maxBuffer: 20480 * 1024, timeout: 45000, killSignal: 'SIGKILL'}, function(err, stdout, stderr) {
        // err.code to 139 means accessibility errors, we don't really care
        if(err && err.code !== 139) {
          return cb(new HydrationError([err, stderr, stdout]));
        }

        pdfFile = '/tmp/' + nativePath.basename(path) + '.pdf';
        cb(null);
      });
    },
    function sendToPdfHydrater(cb) {
      var pdfDocument = {};
      pdfDocument.identifier = document.identifier;
      pdfDocument.document_type = "document";
      pdfDocument.metadata = {};
      pdfDocument.metadata.path = document.metadata.path;

      request(config.pdfHydraterUrl)
        .post('/hydrate')
        .send({
          priority: finalCb.priority,
          file_path: config.officeHydraterUrl + "/document?path=" + pdfFile.replace('/tmp/', ''),
          callback: finalCb.urlCallback,
          document: pdfDocument
        })
        .end(cb);
    },
  ], function(err) {
    if(err && pdfFile) {
      fs.unlink(pdfFile);
    }

    // return null document to skip hydration
    cb(err, null);
  });
};
<commit_msg>If stderr isn't empty, check if file exists<commit_after>'use strict';

/**
 * @file Helper for libreOffice file processing
 * For more information about libreoffice :
 * http://www.libreoffice.org/
 */

var fs = require('fs');
var async = require('async');
var shellExec = require('child_process').exec;
var nativePath = require('path');
var request = require('supertest');
var async = require('async');
var config = require('../config/configuration.js');

var HydrationError = require('anyfetch-hydrater').HydrationError;

var extensions = {
  '.xls': 'calc_pdf_Export',
  '.xlsx': 'calc_pdf_Export',
  '.ods': 'calc_pdf_Export',
  '.doc': 'writer_pdf_Export',
  '.docx': 'writer_pdf_Export',
  '.odt': 'writer_pdf_Export',
  '.rtf': 'writer_pdf_Export',
  '.ppt': 'impress_pdf_Export',
  '.pptx': 'impress_pdf_Export',
  '.odp': 'impress_pdf_Export',
  '.pps': 'impress_pdf_Export',
  '.ppsx': 'impress_pdf_Export',
  '.key': 'impress_pdf_Export',
};

var contentType = {
  "application/msword": 'writer_pdf_Export',
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document": 'writer_pdf_Export',
  "application/vnd.ms-excel": 'calc_pdf_Export',
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": 'calc_pdf_Export',
  "application/vnd.ms-powerpoint": 'impress_pdf_Export',
  "application/vnd.openxmlformats-officedocument.presentationml.presentation": 'impress_pdf_Export',
  "application/vnd.openxmlformats-officedocument.presentationml.slideshow": 'impress_pdf_Export'
};

/**
 * Extract the content in html of the specified document
 *
 * @param {string} path Path of the specified file
 * @param {string} document to hydrate
 * @param {function} cb Callback, first parameter, is the error if any, then the processed data
 */
module.exports = function(path, document, changes, cb) {
  var finalCb = cb;
  var pdfFile;
  async.waterfall([
    function getConverter(cb) {
      var converter;

      if(document.metadata && document.metadata.path) {
        var file = nativePath.basename(document.metadata.path);
        var fileExtension = nativePath.extname(file).toLowerCase();
        if(extensions[fileExtension]) {
          converter = extensions[fileExtension];
        }
      }
      if(document.data && document.data.content_type) {
        if(contentType[document.data.content_type]) {
          converter = contentType[document.data.content_type];
        }
      }

      if(!converter) {
        return cb(new HydrationError("Unknown file extension or content type"));
      }
      else {
        cb(null, converter);
      }
    },
    function convertToPdf(converter, cb) {
      var options = [
        '--headless',
        '--norestore',
        '--nolockcheck'
      ];

      shellExec('loffice ' + options.join(' ') + ' --convert-to pdf:"' + converter + '" --outdir /tmp ' + path, {maxBuffer: 20480 * 1024, timeout: 45000, killSignal: 'SIGKILL'}, function(err, stdout, stderr) {
        // err.code to 139 means accessibility errors, we don't really care
        if(err && err.code !== 139) {
          return cb(new HydrationError([err, stderr, stdout]));
        }

        pdfFile = '/tmp/' + nativePath.basename(path) + '.pdf';

        if(!stderr) {
          return cb(null, true);
        }
        fs.exists(pdfFile, cb);
      });
    },
    function sendToPdfHydrater(cb) {
      var pdfDocument = {};
      pdfDocument.identifier = document.identifier;
      pdfDocument.document_type = "document";
      pdfDocument.metadata = {};
      pdfDocument.metadata.path = document.metadata.path;

      request(config.pdfHydraterUrl)
        .post('/hydrate')
        .send({
          priority: finalCb.priority,
          file_path: config.officeHydraterUrl + "/document?path=" + pdfFile.replace('/tmp/', ''),
          callback: finalCb.urlCallback,
          document: pdfDocument
        })
        .end(cb);
    },
  ], function(err) {
    if(err && pdfFile) {
      fs.unlink(pdfFile);
    }

    // return null document to skip hydration
    cb(err, null);
  });
};
<|endoftext|>


<commit_before>/*
 * Guerilla
 * Copyright 2015, Yahoo Inc.
 * Copyrights licensed under the MIT License.
 *
 * Models the results from an individual job run.
 */

var moment = require('moment');
var path = require('path');
var util = require('util');
var async = require('async');
var fs = require('fs-extra');
var utilities = require(path.join(__rootdir, 'lib', 'utilities'));

var Result;

module.exports.init = function (db) {
	if (Result) return;

	Result = db.define('Result', {
		number: { type: Number, index: true },
		worker_name: String,
		worker_url: String,
		device: db.JSON,
		queued: Date,
		queue_time: Number,
		started: Date,
		run_time: Number,
		finished: Date,
		status: { type: String, default: 'inconclusive', index: true },
		output_dir: String,
		pipeline: db.JSON,
		data: db.JSON,
		reports: db.JSON,
		error_messages: db.JSON,
		notes: String
	});

	Result.validatesPresenceOf('number');
	Result.validatesInclusionOf('status', { in: ['success', 'failure', 'inconclusive', 'running', 'queued'] });

	Result.statics = {
		createForJob: function (job, callback) {
			job.results(function (error, results) {
				if (error)
					return callback(error);

				var result = new Result();
				result.job_id = job.id;
				result.number = results.length + 1;
				result.pipeline = [];
				result.data = {};
				result.reports = [];
				result.error_messages = [];

				result.save(callback);
			});
		},
		findByJobIdAndNumber: function (job_id, number, callback) {
			if (number === 'last') {
				this.getLast(job_id, callback);
			} else if (number === 'current') {
				this.getCurrent(job_id, callback);
			} else {
				Result.findOne()
					.where('job_id', job_id)
					.where('number', number)
					.run({}, callback);
			}
		},
		getLast: function (job_id, callback) {
			Result.findOne()
				.where('job_id', job_id)
				.sort('-number')
                .where('status').nin(['queued', 'running'])
                .run({}, callback);
        },
		getCurrent: function (job_id, callback) {
			Result.findOne()
				.where('job_id', job_id)
				.sort('-number')
				.run({}, callback);
		}
	};

	Result.prototype.log = function (tag, string, callback) {
		callback = callback || function (error) { if (error) logger.e(error) };
		string = String(string);

		try {
			if (this.output_dir) {
				var logString = '[' + moment().format('M/D/YY h:mm:ss A') + '] [' + tag.toUpperCase() + '] ' + string.trim() + '\n';
				logger.v(logString);
				fs.appendFile(path.join(this.output_dir, 'console-log.txt'), logString, function (error) {
					if (callback) callback(error);
				});
			}
			else {
				if (callback) callback(new Error('Cannot log, output_dir not set.'));
			}
		}
		catch (ex) {
			callback(new Error('Error logging.'));
		}
	};

	Result.prototype.addPhase = function (phase, callback) {
		if (phase)
			this.pipeline.push(phase.toJSON());
		this.save(callback);
	};

	Result.prototype.addError = function (error, callback) {
		if (error) {
			this.error_messages.push(error.message || error.toString());
			this.log('error', error.stack || error.toString());
		}
		this.save(callback);
	};

	Result.prototype.addData = function (data, callback) {
		if (utilities.isDictionary(data)) {
			for (var key in data) {
				if (utilities.exists(data[key])) this.data[key] = data[key];
			}
		}

		this.save(callback);
	};

	Result.prototype.addReport = function (config, callback) {
		if (config)
			this.reports.push(config);
		this.save(callback);
	};

	Result.prototype.enqueue = function (callback) {
		this.status = 'queued';
		this.queued = new Date();
		this.save(callback);
	};

	Result.prototype.start = function (callback) {
		var self = this;

		fs.mkdirs(self.output_dir, function (error) {
			self.status = 'running';
			self.started = new Date();
			self.queue_time = self.started - self.queued;
			self.save(function (err) {
				if (error || err) return callback(error || err);
				callback(null, self);
			});
		});
	};

	Result.prototype.interrupt = function (callback) {
		var self = this;

		async.series([
			function (cb) {
				self.addError('Job was interrupted.', cb);
			},
			function (cb) {
				self.status = 'inconclusive';
				self.finished = new Date();
				self.run_time = self.finished - self.started;
				self.save(cb);
			}
		], callback);
	};

	Result.prototype.finish = function (error, callback) {
		var self = this;

		async.series([
			function (cb) {
				if (error) return self.addError(error, cb);
				cb();
			},
			function (cb) {
				if (self.error_messages.length > 0)
					self.status = 'failure';
				else
					self.status = 'success';

				self.finished = new Date();
				self.run_time = self.finished - self.started;
				self.save(cb);
			}
		], callback);
	};

	Result.prototype.previous = function (callback) {
		if (this.number === 1) return callback(new Error('No previous results exist.'));
		Result.statics.findByJobIdAndNumber(this.job_id, this.number - 1, callback);
	};
};
<commit_msg>Simplified code.<commit_after>/*
 * Guerilla
 * Copyright 2015, Yahoo Inc.
 * Copyrights licensed under the MIT License.
 *
 * Models the results from an individual job run.
 */

var moment = require('moment');
var path = require('path');
var util = require('util');
var async = require('async');
var fs = require('fs-extra');
var utilities = require(path.join(__rootdir, 'lib', 'utilities'));

var Result;

module.exports.init = function (db) {
	if (Result) return;

	Result = db.define('Result', {
		number: { type: Number, index: true },
		worker_name: String,
		worker_url: String,
		device: db.JSON,
		queued: Date,
		queue_time: Number,
		started: Date,
		run_time: Number,
		finished: Date,
		status: { type: String, default: 'inconclusive', index: true },
		output_dir: String,
		pipeline: db.JSON,
		data: db.JSON,
		reports: db.JSON,
		error_messages: db.JSON,
		notes: String
	});

	Result.validatesPresenceOf('number');
	Result.validatesInclusionOf('status', { in: ['success', 'failure', 'inconclusive', 'running', 'queued'] });

	Result.statics = {
		createForJob: function (job, callback) {
			job.results(function (error, results) {
				if (error)
					return callback(error);

				var result = new Result();
				result.job_id = job.id;
				result.number = results.length + 1;
				result.pipeline = [];
				result.data = {};
				result.reports = [];
				result.error_messages = [];

				result.save(callback);
			});
		},
		findByJobIdAndNumber: function (job_id, number, callback) {
			if (number === 'last') {
				this.getLast(job_id, callback);
			} else if (number === 'current') {
				this.getCurrent(job_id, callback);
			} else {
				Result.findOne()
					.where('job_id', job_id)
					.where('number', number)
					.run(callback);
			}
		},
		getLast: function (job_id, callback) {
			Result.findOne()
				.where('job_id', job_id)
				.sort('-number')
                .where('status').nin(['queued', 'running'])
                .run(callback);
        },
		getCurrent: function (job_id, callback) {
			Result.findOne()
				.where('job_id', job_id)
				.sort('-number')
				.run(callback);
		}
	};

	Result.prototype.log = function (tag, string, callback) {
		callback = callback || function (error) { if (error) logger.e(error) };
		string = String(string);

		try {
			if (this.output_dir) {
				var logString = '[' + moment().format('M/D/YY h:mm:ss A') + '] [' + tag.toUpperCase() + '] ' + string.trim() + '\n';
				logger.v(logString);
				fs.appendFile(path.join(this.output_dir, 'console-log.txt'), logString, function (error) {
					if (callback) callback(error);
				});
			}
			else {
				if (callback) callback(new Error('Cannot log, output_dir not set.'));
			}
		}
		catch (ex) {
			callback(new Error('Error logging.'));
		}
	};

	Result.prototype.addPhase = function (phase, callback) {
		if (phase)
			this.pipeline.push(phase.toJSON());
		this.save(callback);
	};

	Result.prototype.addError = function (error, callback) {
		if (error) {
			this.error_messages.push(error.message || error.toString());
			this.log('error', error.stack || error.toString());
		}
		this.save(callback);
	};

	Result.prototype.addData = function (data, callback) {
		if (utilities.isDictionary(data)) {
			for (var key in data) {
				if (utilities.exists(data[key])) this.data[key] = data[key];
			}
		}

		this.save(callback);
	};

	Result.prototype.addReport = function (config, callback) {
		if (config)
			this.reports.push(config);
		this.save(callback);
	};

	Result.prototype.enqueue = function (callback) {
		this.status = 'queued';
		this.queued = new Date();
		this.save(callback);
	};

	Result.prototype.start = function (callback) {
		var self = this;

		fs.mkdirs(self.output_dir, function (error) {
			self.status = 'running';
			self.started = new Date();
			self.queue_time = self.started - self.queued;
			self.save(function (err) {
				if (error || err) return callback(error || err);
				callback(null, self);
			});
		});
	};

	Result.prototype.interrupt = function (callback) {
		var self = this;

		async.series([
			function (cb) {
				self.addError('Job was interrupted.', cb);
			},
			function (cb) {
				self.status = 'inconclusive';
				self.finished = new Date();
				self.run_time = self.finished - self.started;
				self.save(cb);
			}
		], callback);
	};

	Result.prototype.finish = function (error, callback) {
		var self = this;

		async.series([
			function (cb) {
				if (error) return self.addError(error, cb);
				cb();
			},
			function (cb) {
				if (self.error_messages.length > 0)
					self.status = 'failure';
				else
					self.status = 'success';

				self.finished = new Date();
				self.run_time = self.finished - self.started;
				self.save(cb);
			}
		], callback);
	};

	Result.prototype.previous = function (callback) {
		if (this.number === 1) return callback(new Error('No previous results exist.'));
		Result.statics.findByJobIdAndNumber(this.job_id, this.number - 1, callback);
	};
};
<|endoftext|>


<commit_before>"use strict";

// An order represents a pending push or shift.
const Order = (channel) => {
  let order;
  const preonFulfilleds = [];

  const promise = new Promise((resolve, reject) => {
    order = {
      resolve: (value) => {
        preonFulfilleds.forEach((preonFulfilled) => {
          preonFulfilled(value);
        });

        resolve(value);
      },

      reject,
    };
  });

  Object.assign(promise, {
    cancel: () => {
      order.cancelled = true;
    },

    channel,

    prethen: (onFulfilled) => {
      preonFulfilleds.push(onFulfilled);
    },
  });

  return { order, promise };
};

const prototype = {};

// Create a new channel with a buffer the size of "length".
const Channel = function(length = 0) {
  let buffered = 0;
  let closed = false;
  let lastValue;
  const pushes = [];
  const shifts = [];

  const matchPushesAndShifts = (index) => {
    while (index.push < pushes.length && index.shift < shifts.length) {
      const push = pushes[index.push];
      const shift = shifts[index.shift];

      if (push.cancelled) {
        index.push++;
      } else if (shift.cancelled) {
        index.shift++;
      } else {
        lastValue = push.value;
        shift.resolve(lastValue);
        index.shift++;
        push.resolve(length);
        index.push++;
        buffered = Math.max(0, buffered - 1);
      }
    }
  };

  // Resolve push promises up to the end of the buffer.
  const resolveBufferedPushes = (index) => {
    for (
      let resolvedIndex = index.push + buffered;
      resolvedIndex < pushes.length && buffered < length;
      resolvedIndex++
    ) {
      const { cancelled, resolve } = pushes[resolvedIndex];

      if (!cancelled) {
        buffered++;
        resolve(length);
      }
    }
  };

  const resolveClosedShifts = (index) => {
    for (; index.shift < shifts.length; index.shift++) {
      const { cancelled, resolve } = shifts[index.shift];

      if (!cancelled) {
        lastValue = undefined;
        resolve(lastValue);
      }
    }
  };

  // Process the push and shift queues like an order book, looking for matches.
  const processOrders = () => {
    const index = { push: 0, shift: 0 };
    matchPushesAndShifts(index);
    resolveBufferedPushes(index);

    // If the channel is closed then resolve 'undefined' to remaining shifts.
    if (closed) {
      resolveClosedShifts(index);
    }

    pushes.splice(0, index.push);
    shifts.splice(0, index.shift);
  };

  const readOnly = Object.freeze(
    Object.assign(Object.create(prototype), {
      concat: (...args) => {
        const output = Channel();

        (async () => {
          await readOnly.forEach(output.push);

          for (let index = 0; index < args.length; index++) {
            const arg = args[index];

            /* For some reason the following code works in Mocha but not in
              Wallaby.js:

              await (Channel.isChannel(arg)
                ? arg.forEach(output.push)
                : output.push(arg));

              If we use the 'if' statement below then Wallaby.js is happy.
            */

            if (Channel.isChannel(arg)) {
              await arg.forEach(output.push);
            } else {
              await output.push(arg);
            }
          }

          await output.close();
        })();

        return output;
      },

      every: async (callbackfn, thisArg) => {
        for (;;) {
          const value = await readOnly.shift();

          if (value === undefined) {
            return true;
          } else {
            if (!callbackfn.call(thisArg, value)) {
              return false;
            }
          }
        }
      },

      filter: (callbackfn, thisArg) => {
        const output = Channel();

        (async () => {
          await readOnly.forEach(async (value) => {
            if (await callbackfn.call(thisArg, value)) {
              await output.push(value);
            }
          });

          await output.close();
        })();

        return output;
      },

      flat: (depth) => {
        const output = Channel();

        (async () => {
          await readOnly.forEach(async (value) => {
            if (Channel.isChannel(value)) {
              const input = depth > 1 ? value.flat(depth - 1) : value;
              await input.forEach(output.push);
            } else {
              await output.push(value);
            }
          });

          await output.close();
        })();

        return output;
      },

      flatMap: (mapperFunction, thisArg) =>
        readOnly.map(mapperFunction, thisArg).flat(),

      forEach: async (callbackfn, thisArg) => {
        for (;;) {
          const value = await readOnly.shift();

          if (value === undefined) {
            break;
          } else {
            await callbackfn.call(thisArg, value);
          }
        }
      },

      join: async (separator) => (await readOnly.values()).join(separator),

      map: (callbackfn, thisArg) => {
        const output = Channel();

        (async () => {
          await readOnly.forEach((value) =>
            output.push(callbackfn.call(thisArg, value))
          );

          await output.close();
        })();

        return output;
      },

      readOnly: () => readOnly,

      reduce: async (callbackfn, ...initialValue) => {
        let previousValue = initialValue[0];
        let previousValueDefined = initialValue.length > 0;

        await readOnly.forEach((currentValue) => {
          if (previousValueDefined) {
            previousValue = callbackfn(previousValue, currentValue);
          } else {
            previousValue = currentValue;
            previousValueDefined = true;
          }
        });

        if (previousValueDefined) {
          return previousValue;
        } else {
          throw new TypeError(
            `No values in channel and initialValue wasn't provided.`
          );
        }
      },

      shift: function() {
        const { order, promise } = Order(this);
        shifts.push(order);
        queueMicrotask(processOrders);

        // Don't freeze promise because Bluebird expects it to be mutable.
        return promise;
      },

      slice: (start = 0, end = Infinity) => {
        const output = Channel();

        (async () => {
          // Consume values before the starting point.
          for (let index = 0; index < start; index++) {
            await readOnly.shift();
          }

          for (let index = start; index < end; index++) {
            const value = await readOnly.shift();

            if (value === undefined) {
              break;
            } else {
              await output.push(value);
            }
          }

          await output.close();
        })();

        return output;
      },

      some: async (callbackfn, thisArg) => {
        for (;;) {
          const value = await readOnly.shift();

          if (value === undefined) {
            return false;
          } else {
            if (callbackfn.call(thisArg, value)) {
              return true;
            }
          }
        }
      },

      toString: () => `Channel(${length})`,

      value: () => lastValue,

      values: async () => {
        const array = [];

        await readOnly.forEach((item) => {
          array.push(item);
        });

        return array;
      },
    })
  );

  const writeOnly = Object.freeze(
    Object.assign(Object.create(prototype), {
      close: () =>
        new Promise((resolve, reject) => {
          if (closed) {
            reject(new Error(`Can't close an already-closed channel.`));
          } else {
            closed = true;
            processOrders();

            // Give remaining orders in flight time to resolve before returning.
            queueMicrotask(resolve);
          }
        }),

      length,

      push: function(value) {
        const { order, promise } = Order(this);
        order.value = value;

        // If value is a promise that rejects, catch it in case there hasn't
        // been a matching shift yet in order to prevent an unhandledRejection
        // error.
        Promise.resolve(value).catch(() => {});

        if (closed) {
          order.reject(new Error(`Can't push to closed channel.`));
        } else if (value === undefined) {
          order.reject(
            new TypeError(
              `Can't push 'undefined' to channel, use close instead.`
            )
          );
        } else if (arguments.length > 1) {
          order.reject(new Error(`Can't push more than one value at a time.`));
        } else {
          pushes.push(order);
          queueMicrotask(processOrders);
        }

        // Don't freeze promise because Bluebird expects it to be mutable.
        return promise;
      },

      writeOnly: () => writeOnly,
    })
  );

  return Object.freeze(
    Object.assign(Object.create(prototype), readOnly, writeOnly)
  );
};

Channel.all = (channels) => {
  const output = Channel();

  (async () => {
    for (;;) {
      const values = await Promise.all(channels.map(Channel.shift));

      if (values.every((value) => value === undefined)) {
        break;
      } else {
        await output.push(values);
      }
    }

    await output.close();
  })();

  return output;
};

// Node.js stream.readable
const fromNodeStream = (channel, stream) => {
  stream.on(`readable`, async () => {
    for (;;) {
      const data = stream.read();

      if (data === null) {
        break;
      } else {
        await channel.push(data);
      }
    }
  });

  stream.once(`end`, channel.close);
};

Channel.from = (values, mapfn, thisArg) => {
  const channel = Channel();

  (async () => {
    // iterable
    try {
      for (let value of values) {
        await channel.push(value);
      }

      await channel.close();
    } catch (exception) {
      // callback function
      try {
        for (;;) {
          const value = values();

          if (value === undefined) {
            await channel.close();
            break;
          } else {
            await channel.push(value);
          }
        }
      } catch (exception) {
        fromNodeStream(channel, values);
      }
    }
  })();

  return (mapfn ? channel.map(mapfn, thisArg) : channel).readOnly();
};

Channel.of = (...values) => Channel.from(values);

Channel.isChannel = (value) =>
  value !== undefined &&
  value !== null &&
  Object.getPrototypeOf(value) === prototype;

Channel.select = (methodPromises) => {
  if (!Array.isArray(methodPromises)) {
    throw new TypeError(`Channel.select: Argument must be an array.`);
  }

  const selectPromise = new Promise((resolve, reject) => {
    methodPromises.forEach(async (promise) => {
      try {
        promise.prethen(() => {
          // We've been given a heads-up that this method will complete first
          // so cancel the other method calls.
          methodPromises.forEach((other) => {
            if (other !== promise) {
              other.cancel();
            }
          });
        });

        try {
          await promise;
        } catch (exception) {
          reject(exception);
        }

        resolve(promise.channel);
      } catch (exception) {
        reject(
          new TypeError(
            `Channel.select accepts only promises returned by push & shift.`
          )
        );
      }
    });
  });

  return Object.assign(selectPromise, {
    cancel: () => methodPromises.forEach((promise) => promise.cancel()),
  });
};

// functional interface allowing full or partial application
//
// Channel.slice(10, Infinity, channel)
// Channel.slice(10, Infinity)(channel)
// Channel.slice(10)(Infinity)(channel)

const channel = Channel();

const methods = Object.keys(channel).filter(
  (method) => typeof channel[method] === `function`
);

const arities = {
  reduce: 2,
  slice: 2,
};

methods.forEach((method) => {
  const bound = function(...args) {
    const arity = arities[method] || channel[method].length;

    return args.length > arity
      ? args[arity][method](...args.slice(0, arity))
      : bound.bind(this, ...args);
  };

  Channel[method] = bound;
});

module.exports = Object.freeze(Channel);
<commit_msg>Rewrite `flat` and `flatMap`.<commit_after>"use strict";

// An order represents a pending push or shift.
const Order = (channel) => {
  let order;
  const preonFulfilleds = [];

  const promise = new Promise((resolve, reject) => {
    order = {
      resolve: (value) => {
        preonFulfilleds.forEach((preonFulfilled) => {
          preonFulfilled(value);
        });

        resolve(value);
      },

      reject,
    };
  });

  Object.assign(promise, {
    cancel: () => {
      order.cancelled = true;
    },

    channel,

    prethen: (onFulfilled) => {
      preonFulfilleds.push(onFulfilled);
    },
  });

  return { order, promise };
};

const prototype = {};

// Create a new channel with a buffer the size of "length".
const Channel = function(length = 0) {
  let buffered = 0;
  let closed = false;
  let lastValue;
  const pushes = [];
  const shifts = [];

  const matchPushesAndShifts = (index) => {
    while (index.push < pushes.length && index.shift < shifts.length) {
      const push = pushes[index.push];
      const shift = shifts[index.shift];

      if (push.cancelled) {
        index.push++;
      } else if (shift.cancelled) {
        index.shift++;
      } else {
        lastValue = push.value;
        shift.resolve(lastValue);
        index.shift++;
        push.resolve(length);
        index.push++;
        buffered = Math.max(0, buffered - 1);
      }
    }
  };

  // Resolve push promises up to the end of the buffer.
  const resolveBufferedPushes = (index) => {
    for (
      let resolvedIndex = index.push + buffered;
      resolvedIndex < pushes.length && buffered < length;
      resolvedIndex++
    ) {
      const { cancelled, resolve } = pushes[resolvedIndex];

      if (!cancelled) {
        buffered++;
        resolve(length);
      }
    }
  };

  const resolveClosedShifts = (index) => {
    for (; index.shift < shifts.length; index.shift++) {
      const { cancelled, resolve } = shifts[index.shift];

      if (!cancelled) {
        lastValue = undefined;
        resolve(lastValue);
      }
    }
  };

  // Process the push and shift queues like an order book, looking for matches.
  const processOrders = () => {
    const index = { push: 0, shift: 0 };
    matchPushesAndShifts(index);
    resolveBufferedPushes(index);

    // If the channel is closed then resolve 'undefined' to remaining shifts.
    if (closed) {
      resolveClosedShifts(index);
    }

    pushes.splice(0, index.push);
    shifts.splice(0, index.shift);
  };

  const readOnly = Object.freeze(
    Object.assign(Object.create(prototype), {
      concat: (...args) => {
        const output = Channel();

        (async () => {
          await readOnly.forEach(output.push);

          for (let index = 0; index < args.length; index++) {
            const arg = args[index];

            /* For some reason the following code works in Mocha but not in
              Wallaby.js:

              await (Channel.isChannel(arg)
                ? arg.forEach(output.push)
                : output.push(arg));

              If we use the 'if' statement below then Wallaby.js is happy.
            */

            if (Channel.isChannel(arg)) {
              await arg.forEach(output.push);
            } else {
              await output.push(arg);
            }
          }

          await output.close();
        })();

        return output;
      },

      every: async (callbackfn, thisArg) => {
        for (;;) {
          const value = await readOnly.shift();

          if (value === undefined) {
            return true;
          } else {
            if (!callbackfn.call(thisArg, value)) {
              return false;
            }
          }
        }
      },

      filter: (callbackfn, thisArg) => {
        const output = Channel();

        (async () => {
          await readOnly.forEach(async (value) => {
            if (await callbackfn.call(thisArg, value)) {
              await output.push(value);
            }
          });

          await output.close();
        })();

        return output;
      },

      flat: (depth) =>
        readOnly.flatMap((value) =>
          Channel.isChannel(value)
            ? depth > 1
              ? value.flat(depth - 1)
              : value
            : Channel.of(value)
        ),

      flatMap: (mapperFunction, thisArg) => {
        const output = Channel();

        (async () => {
          await readOnly.forEach(async (value) => {
            await mapperFunction.call(thisArg, value).forEach(output.push);
          });

          await output.close();
        })();

        return output;
      },

      forEach: async (callbackfn, thisArg) => {
        for (;;) {
          const value = await readOnly.shift();

          if (value === undefined) {
            break;
          } else {
            await callbackfn.call(thisArg, value);
          }
        }
      },

      join: async (separator) => (await readOnly.values()).join(separator),

      map: (callbackfn, thisArg) => {
        const output = Channel();

        (async () => {
          await readOnly.forEach((value) =>
            output.push(callbackfn.call(thisArg, value))
          );

          await output.close();
        })();

        return output;
      },

      readOnly: () => readOnly,

      reduce: async (callbackfn, ...initialValue) => {
        let previousValue = initialValue[0];
        let previousValueDefined = initialValue.length > 0;

        await readOnly.forEach((currentValue) => {
          if (previousValueDefined) {
            previousValue = callbackfn(previousValue, currentValue);
          } else {
            previousValue = currentValue;
            previousValueDefined = true;
          }
        });

        if (previousValueDefined) {
          return previousValue;
        } else {
          throw new TypeError(
            `No values in channel and initialValue wasn't provided.`
          );
        }
      },

      shift: function() {
        const { order, promise } = Order(this);
        shifts.push(order);
        queueMicrotask(processOrders);

        // Don't freeze promise because Bluebird expects it to be mutable.
        return promise;
      },

      slice: (start = 0, end = Infinity) => {
        const output = Channel();

        (async () => {
          // Consume values before the starting point.
          for (let index = 0; index < start; index++) {
            await readOnly.shift();
          }

          for (let index = start; index < end; index++) {
            const value = await readOnly.shift();

            if (value === undefined) {
              break;
            } else {
              await output.push(value);
            }
          }

          await output.close();
        })();

        return output;
      },

      some: async (callbackfn, thisArg) => {
        for (;;) {
          const value = await readOnly.shift();

          if (value === undefined) {
            return false;
          } else {
            if (callbackfn.call(thisArg, value)) {
              return true;
            }
          }
        }
      },

      toString: () => `Channel(${length})`,

      value: () => lastValue,

      values: async () => {
        const array = [];

        await readOnly.forEach((item) => {
          array.push(item);
        });

        return array;
      },
    })
  );

  const writeOnly = Object.freeze(
    Object.assign(Object.create(prototype), {
      close: () =>
        new Promise((resolve, reject) => {
          if (closed) {
            reject(new Error(`Can't close an already-closed channel.`));
          } else {
            closed = true;
            processOrders();

            // Give remaining orders in flight time to resolve before returning.
            queueMicrotask(resolve);
          }
        }),

      length,

      push: function(value) {
        const { order, promise } = Order(this);
        order.value = value;

        // If value is a promise that rejects, catch it in case there hasn't
        // been a matching shift yet in order to prevent an unhandledRejection
        // error.
        Promise.resolve(value).catch(() => {});

        if (closed) {
          order.reject(new Error(`Can't push to closed channel.`));
        } else if (value === undefined) {
          order.reject(
            new TypeError(
              `Can't push 'undefined' to channel, use close instead.`
            )
          );
        } else if (arguments.length > 1) {
          order.reject(new Error(`Can't push more than one value at a time.`));
        } else {
          pushes.push(order);
          queueMicrotask(processOrders);
        }

        // Don't freeze promise because Bluebird expects it to be mutable.
        return promise;
      },

      writeOnly: () => writeOnly,
    })
  );

  return Object.freeze(
    Object.assign(Object.create(prototype), readOnly, writeOnly)
  );
};

Channel.all = (channels) => {
  const output = Channel();

  (async () => {
    for (;;) {
      const values = await Promise.all(channels.map(Channel.shift));

      if (values.every((value) => value === undefined)) {
        break;
      } else {
        await output.push(values);
      }
    }

    await output.close();
  })();

  return output;
};

// Node.js stream.readable
const fromNodeStream = (channel, stream) => {
  stream.on(`readable`, async () => {
    for (;;) {
      const data = stream.read();

      if (data === null) {
        break;
      } else {
        await channel.push(data);
      }
    }
  });

  stream.once(`end`, channel.close);
};

Channel.from = (values, mapfn, thisArg) => {
  const channel = Channel();

  (async () => {
    // iterable
    try {
      for (let value of values) {
        await channel.push(value);
      }

      await channel.close();
    } catch (exception) {
      // callback function
      try {
        for (;;) {
          const value = values();

          if (value === undefined) {
            await channel.close();
            break;
          } else {
            await channel.push(value);
          }
        }
      } catch (exception) {
        fromNodeStream(channel, values);
      }
    }
  })();

  return (mapfn ? channel.map(mapfn, thisArg) : channel).readOnly();
};

Channel.of = (...values) => Channel.from(values);

Channel.isChannel = (value) =>
  value !== undefined &&
  value !== null &&
  Object.getPrototypeOf(value) === prototype;

Channel.select = (methodPromises) => {
  if (!Array.isArray(methodPromises)) {
    throw new TypeError(`Channel.select: Argument must be an array.`);
  }

  const selectPromise = new Promise((resolve, reject) => {
    methodPromises.forEach(async (promise) => {
      try {
        promise.prethen(() => {
          // We've been given a heads-up that this method will complete first
          // so cancel the other method calls.
          methodPromises.forEach((other) => {
            if (other !== promise) {
              other.cancel();
            }
          });
        });

        try {
          await promise;
        } catch (exception) {
          reject(exception);
        }

        resolve(promise.channel);
      } catch (exception) {
        reject(
          new TypeError(
            `Channel.select accepts only promises returned by push & shift.`
          )
        );
      }
    });
  });

  return Object.assign(selectPromise, {
    cancel: () => methodPromises.forEach((promise) => promise.cancel()),
  });
};

// functional interface allowing full or partial application
//
// Channel.slice(10, Infinity, channel)
// Channel.slice(10, Infinity)(channel)
// Channel.slice(10)(Infinity)(channel)

const channel = Channel();

const methods = Object.keys(channel).filter(
  (method) => typeof channel[method] === `function`
);

const arities = {
  reduce: 2,
  slice: 2,
};

methods.forEach((method) => {
  const bound = function(...args) {
    const arity = arities[method] || channel[method].length;

    return args.length > arity
      ? args[arity][method](...args.slice(0, arity))
      : bound.bind(this, ...args);
  };

  Channel[method] = bound;
});

module.exports = Object.freeze(Channel);
<|endoftext|>


<commit_before>/**
 * @fileoverview Main HARToPAge object.
 * @author Peter Hedenskog
 * @copyright (c) 2015, Peter Hedenskog, Tobias Lidskog.
 * Released under the Apache 2.0 License.
 */

'use strict';

// TODO:
// handling multiple har:s in one file
// return a clean setup
// get the httpVersion of the main page
// add timings?
// domains
// assets per domain
// weight per domain

var util = require('./util');

var assets = [];
var responseCodes = {};
var domains = {};
var pages = [];
var myPage = {};

/**
 * Collect information about a response (asset).
 * @param {Object} entry The HAR entry.
 * @returns {Object} A simplified asset.
 * @private
 */
function collectAsset(entry) {
  var contentType = util.getContentType(entry.response.content.mimeType);
  var requestHeaders = util.flattenHeaders(entry.request.headers);
  var responseHeaders = util.flattenHeaders(entry.response.headers);
  return {
    type: contentType,
    url: entry.request.url,
    size: entry.response.headersSize + entry.response.bodySize,
    expires: util.getExpires(responseHeaders),
    status: entry.response.status,
    timeSinceLastModified: util.getTimeSinceLastModified(responseHeaders),
    httpVersion: entry.response.httpVersion,
    headers: {
      request: requestHeaders,
      response: responseHeaders
    }
  };
}

/**
 * Collect information about the domain for the asset.
 * @param {Object} asset The asset.
 * @returns {void}
 * @private
 */
function collectDomainInfo(asset) {
  var domain = util.getHostname(asset.url);
  if (domains[domain]) {
    domains[domain].size += asset.size;
    domains[domain].requests += 1;
  } else {
    domains[domain] = {
      requests: 1,
      size: asset.size
    };
  }
}

/**
 * Collect the response code from a asset.
 * @param {Object} asset The asset.
 * @returns {void}
 * @private
 */
function collectResponseCode(asset) {
  if (responseCodes[asset.status]) {
    responseCodes[asset.status]++;
  } else {
    responseCodes[asset.status] = 1;
  }
}

/**
 * Collect content type from an asset.
 * @param {Object} asset The asset.
 * @returns {void}
 * @private
 */
function collectContentType(asset) {
  if (asset.status === 200) {

    // TODO how to handle unknown?
    if (myPage[asset.type]) {
      myPage[asset.type].requests++;
      // header vs content size?
      myPage[asset.type].size += asset.size;
    } else {
      myPage[asset.type] = {
        size: asset.size,
        requests: 1
      };
    }
  }
}

module.exports = {

  /**
   * Convert a HAR file object to a Page.
   * @param {Object} har The har to process.
   * @returns {Object} The converted page object.
   */

  convert: function(har) {
    var testedPages = {};
    har.log.entries.forEach(function(entry) {
      if (!testedPages[entry.pageref]) {
        myPage = {
          url: har.log.entries[0].request.url,
          // TODO handle redirects
          baseDomain: util.getHostname(util.getFinalURL(entry, har)),
          size: 0,
          requests: 0
        };
        testedPages[entry.pageref] = myPage;
        pages.push(myPage);
      }
      var asset = collectAsset(entry);
      assets.push(asset);
      collectDomainInfo(asset);
      collectResponseCode(asset);
      collectContentType(asset);

      if (entry.response.status === 200) {
        myPage.size += entry.response.content.size;
      }
      myPage.requests++;
      myPage.domains = domains;
      myPage.responseCodes = responseCodes;
      myPage.assets = assets;

    });

    return pages;
  }
};
<commit_msg>get HTTP version for first URL<commit_after>/**
 * @fileoverview Main HARToPAge object.
 * @author Peter Hedenskog
 * @copyright (c) 2015, Peter Hedenskog, Tobias Lidskog.
 * Released under the Apache 2.0 License.
 */

'use strict';

// TODO:
// add timings?

var util = require('./util');

var assets = [];
var responseCodes = {};
var domains = {};
var pages = [];
var myPage = {};

/**
 * Collect information about a response (asset).
 * @param {Object} entry The HAR entry.
 * @returns {Object} A simplified asset.
 * @private
 */
function collectAsset(entry) {
  var contentType = util.getContentType(entry.response.content.mimeType);
  var requestHeaders = util.flattenHeaders(entry.request.headers);
  var responseHeaders = util.flattenHeaders(entry.response.headers);
  return {
    type: contentType,
    url: entry.request.url,
    size: entry.response.headersSize + entry.response.bodySize,
    expires: util.getExpires(responseHeaders),
    status: entry.response.status,
    timeSinceLastModified: util.getTimeSinceLastModified(responseHeaders),
    httpVersion: entry.response.httpVersion,
    headers: {
      request: requestHeaders,
      response: responseHeaders
    }
  };
}

/**
 * Collect information about the domain for the asset.
 * @param {Object} asset The asset.
 * @returns {void}
 * @private
 */
function collectDomainInfo(asset) {
  var domain = util.getHostname(asset.url);
  if (domains[domain]) {
    domains[domain].size += asset.size;
    domains[domain].requests += 1;
  } else {
    domains[domain] = {
      requests: 1,
      size: asset.size
    };
  }
}

/**
 * Collect the response code from a asset.
 * @param {Object} asset The asset.
 * @returns {void}
 * @private
 */
function collectResponseCode(asset) {
  if (responseCodes[asset.status]) {
    responseCodes[asset.status]++;
  } else {
    responseCodes[asset.status] = 1;
  }
}

/**
 * Collect content type from an asset.
 * @param {Object} asset The asset.
 * @returns {void}
 * @private
 */
function collectContentType(asset) {
  if (asset.status === 200) {

    // TODO how to handle unknown?
    if (myPage[asset.type]) {
      myPage[asset.type].requests++;
      // header vs content size?
      myPage[asset.type].size += asset.size;
    } else {
      myPage[asset.type] = {
        size: asset.size,
        requests: 1
      };
    }
  }
}

module.exports = {

  /**
   * Convert a HAR file object to a Page.
   * @param {Object} har The har to process.
   * @returns {Object} The converted page object.
   */

  convert: function(har) {
    var testedPages = {};
    har.log.entries.forEach(function(entry) {
      if (!testedPages[entry.pageref]) {
        myPage = {
          url: har.log.entries[0].request.url,
          // TODO handle redirects
          baseDomain: util.getHostname(util.getFinalURL(entry, har)),
          size: 0,
          requests: 0,
          // TODO this will not be right if redirected
          httpVersion: har.log.entries[0].response.httpVersion,
        };
        testedPages[entry.pageref] = myPage;
        pages.push(myPage);
      }
      var asset = collectAsset(entry);
      assets.push(asset);
      collectDomainInfo(asset);
      collectResponseCode(asset);
      collectContentType(asset);

      if (entry.response.status === 200) {
        myPage.size += entry.response.content.size;
      }
      myPage.requests++;
      myPage.domains = domains;
      myPage.responseCodes = responseCodes;
      myPage.assets = assets;

    });

    return pages;
  }
};
<|endoftext|>


<commit_before>/**
 * Answer model
 */
var mongoose = require('mongoose');
var ObjectId = mongoose.Schema.Types.ObjectId;

var answerSchema = new mongoose.Schema({
	author          : {type: ObjectId, ref: 'users'},
	translation 	: String,
	supplementary	: String,
	upvotes			: Number,
	downvotes		: Number,
	score			: Number,
	timestamp   	: Number,
	comments 	    : [{
		text        : String,
		timestamp   : Number,
		userID      : {type: ObjectId, ref: 'users'}
	}]
});

module.exports = mongoose.model('answers', answerSchema);
<commit_msg>Added a “question” field to answers. Now it’s really easy to lookup questions based on their answers.<commit_after>/**
 * Answer model
 */
var mongoose = require('mongoose');
var ObjectId = mongoose.Schema.Types.ObjectId;

var answerSchema = new mongoose.Schema({
	author			: {type: ObjectId, ref: 'users'},
	question		: {type: ObjectId, ref: 'questions'},
	translation		: String,
	supplementary	: String,
	upvotes			: Number,
	downvotes		: Number,
	score			: Number,
	timestamp		: Number,
	comments		: [{
		text		: String,
		timestamp	: Number,
		userID		: {type: ObjectId, ref: 'users'}
	}]
});

module.exports = mongoose.model('answers', answerSchema);
<|endoftext|>