<commit_before>import { Button, Divider, Icon } from 'react-native-elements';
import {
  Linking,
  Platform,
  Share,
  Text,
  TouchableHighlight,
  View,
} from 'react-native';
import Accordion from 'react-native-collapsible/Accordion';
import PropTypes from 'prop-types';
import React from 'react';
import moment from 'moment';
import 'moment/locale/de';

import { ORIGIN } from '../../../globals';
import { breakLongWords } from '../../../utils/strings';
import {
  getLawNameAndUrl,
  getPublicBodyNameAndJurisdiction,
} from '../../../utils/fakeApi';
import { getPrintableStatus } from '../../../utils';
import { grey, primaryColor } from '../../../globals/colors';
import { styles } from './styles';
import { styles as tableStyles } from '../Table/styles';
import BlankContainer from '../BlankContainer';
import Heading from '../Heading';
import I18n from '../../../i18n';
import Link from '../Link';
import NavBarIcon from '../../foiRequests/NavBarIcon';
import SubHeading from '../SubHeading';
import Table from '../Table';

class FoiRequestDetails extends React.Component {
  componentDidMount() {
    const locale = I18n.currentLocale().substring(0, 2);
    moment.locale(locale);
  }

  _renderMessageHeader = msg => (
    <View style={[tableStyles.row, styles.msgHeader]}>
      <Text selectable style={[tableStyles.item1, styles.link]}>
        {`${moment(msg.timestamp).format('DD.MM.YYYY')}:`}
      </Text>
      <Text selectable style={[tableStyles.item2, styles.link]}>
        {msg.sender}
      </Text>
    </View>
  );

  _renderAttachments = attachments => {
    return attachments.map(att => {
      const isPdf =
        att.filetype === 'application/pdf' ||
        att.name.toLowerCase().endsWith('.pdf'); // hotfix for #38
      let viewPdfButton;

      if (isPdf) {
        viewPdfButton = (
          <Button
            containerViewStyle={styles.viewPdfButton}
            backgroundColor={primaryColor}
            title={I18n.t('foiRequestDetails.viewPdf')}
            onPress={() => this.props.navigateToPdfViewer({ uri: att.url })}
            icon={{ name: 'remove-red-eye', color: 'white' }}
          />
        );
      }

      return (
        <View key={att.id}>
          <View style={styles.attachmentsRowLabel}>
            <View>
              <Icon name="attach-file" />
            </View>
            <View>
              <Text style={styles.hotfixTextPadding}>
                {att.name.replace(/_|-/g, ' ')}
              </Text>
            </View>
          </View>
          <View style={styles.attachmentsRowButton}>
            <View>
              <Button
                containerViewStyle={styles.downloadButton}
                backgroundColor={primaryColor}
                title={I18n.t('foiRequestDetails.download')}
                icon={{ name: 'file-download', color: 'white' }}
                onPress={() => Linking.openURL(att.url)}
              />
            </View>
            {viewPdfButton}
          </View>
          <Divider style={styles.dividerAttachments} />
        </View>
      );
    });
  };

  _renderMessageContent = msg => {
    let escalation = null;

    if (msg.is_escalation) {
      escalation = (
        <View style={tableStyles.row}>
          <Text style={tableStyles.item1}>
            {I18n.t('foiRequestDetails.esclatedTo')}
          </Text>
          <TouchableHighlight
            style={[tableStyles.item2, styles.linkTouchable]}
            underlayColor={grey}
          >
            <Text selectable style={styles.link}>
              {msg.recipient_public_body}
            </Text>
          </TouchableHighlight>
        </View>
      );
    }

    return (
      <View style={styles.msgContent}>
        {this._renderAttachments(msg.attachments)}
        <View style={tableStyles.row}>
          <Text style={tableStyles.item1}>
            {I18n.t('foiRequestDetails.from')}
          </Text>
          <Text selectable style={tableStyles.item2}>
            {msg.sender}
          </Text>
        </View>
        <View style={tableStyles.row}>
          <Text style={tableStyles.item1}>
            {I18n.t('foiRequestDetails.on')}
          </Text>
          <Text selectable style={tableStyles.item2}>
            {moment(msg.timestamp).format('LLLL')}
          </Text>
        </View>
        {escalation}
        <View style={tableStyles.row}>
          <Text style={tableStyles.item1}>
            {I18n.t('foiRequestDetails.subject')}
          </Text>
          <Text selectable style={tableStyles.item2}>
            {msg.subject}
          </Text>
        </View>
        <Divider style={styles.dividerBeforeMessageContent} />
        <Text selectable>
          {msg.content_hidden
            ? I18n.t('foiRequestDetails.notYetVisible')
            : msg.content.trim()}
        </Text>
      </View>
    );
  };

  _renderTable = () => {
    const {
      status,
      resolution,
      refusal_reason: refusalReason,
      costs,
      last_message: lastMessage,
      first_message: firstMessage,
      due_date: dueDate,
      law,
    } = this.props.request;

    const { realStatus } = getPrintableStatus(status, resolution);

    const tableData = [
      {
        label: I18n.t('status'),
        value: <Text selectable>{I18n.t(realStatus)}</Text>,
      },
    ];

    if (refusalReason) {
      tableData.push({
        label: I18n.t('foiRequestDetails.refusalReason'),
        value: <Text selectable>{refusalReason}</Text>,
      });
    }

    if (costs && costs !== 0) {
      tableData.push({
        label: I18n.t('foiRequestDetails.costs'),
        value: <Text selectable>{costs}</Text>,
      });
    }

    tableData.push({
      label: I18n.t('foiRequestDetails.startedOn'),
      value: <Text selectable>{moment(firstMessage).format('LLL')}</Text>,
    });

    tableData.push({
      label: I18n.t('foiRequestDetails.lastMessage'),
      value: <Text selectable>{moment(lastMessage).format('LLL')}</Text>,
    });

    if (dueDate) {
      tableData.push({
        label: I18n.t('foiRequestDetails.dueDate'),
        value: <Text selectable>{moment(dueDate).format('LL')}</Text>,
      });
    }

    const { name: lawName, site_url: lawUrl } = getLawNameAndUrl(law);

    if (lawName && lawUrl) {
      tableData.push({
        label: I18n.t('foiRequestDetails.law'),
        value: <Link label={breakLongWords(lawName)} url={lawUrl} />,
      });
    }

    return (
      <View style={styles.table}>

      </View>
    );
  };

  _renderMessages = () => {
    const filtedMessages = this.props.request.messages.filter(
      x => !x.not_publishable
    );
    const messages = filtedMessages.map(
      ({
        id,
        sender,
        subject,
        content,
        timestamp,
        is_response,
        attachments,
        content_hidden,
        is_escalation,
        recipient_public_body,
      }) => {
        const filteredAttachments = attachments
          .filter(x => x.approved)
          .map(x => {
            return {
              key: x.id,
              url: x.site_url,
              name: x.name,
              filetype: x.filetype,
            };
          });
        return {
          key: id,
          sender,
          subject,
          content,
          timestamp,
          content_hidden,
          is_escalation,
          recipient_public_body,
          isRespsone: is_response,
          attachments: filteredAttachments,
        };
      }
    );

    return (
      <View style={styles.msgContainer}>
        <Accordion
          align={'center'}
          duration={1000}
          sections={messages}
          renderHeader={this._renderMessageHeader}
          renderContent={this._renderMessageContent}
          underlayColor={grey}
        />
      </View>
    );
  };

  render() {
    const { title, public_body: publicBody, description } = this.props.request;
    let subheading = (
      <SubHeading style={styles.subheading}>
        {I18n.t('foiRequestDetails.notYetSpecified')}
      </SubHeading>
    );
    if (publicBody) {
      const {
        publicBodyName,
        jurisdictionName,
        publicBodyId,
      } = getPublicBodyNameAndJurisdiction(publicBody);
      subheading = (
        <View>
          <TouchableHighlight
            style={{
              alignSelf: 'center',
            }}
            underlayColor={grey}
            onPress={() => this.props.navigateToPublicBody({ publicBodyId })}
          >
            <View>
              <SubHeading style={[styles.subheading, styles.link]}>
                {publicBodyName}
              </SubHeading>
            </View>
          </TouchableHighlight>
          <Text selectable style={[styles.subheadingJurisdiction]}>
            ({jurisdictionName})
          </Text>
        </View>
      );
    }

    return (
      <BlankContainer>
        <Heading style={styles.heading}>{title}</Heading>
        <View>
          <Text style={styles.subheadingTo}>
            {I18n.t('foiRequestDetails.to')}
          </Text>
          {subheading}
        </View>
        {this._renderTable()}
        <View style={styles.summary}>
          <Text selectable>{description}</Text>
        </View>
        {this._renderMessages()}
      </BlankContainer>
    );
  }
}

FoiRequestDetails.navigationOptions = ({ navigation }) => {
  const requestId = navigation.state.params.request.id;
  function share() {
    Share.share(
      {
        message: 'Check out this Freedom of Information request!',
        url: `${ORIGIN}/a/${requestId}`,
        title: 'FragDenStaat.de',
      },
      {
        // Android only:
        dialogTitle: 'FragDenStaat.de',
      }
    );
  }

  let iconName = 'share';
  let iconType = 'material';

  // platform specific share button
  if (Platform.OS === 'ios') {
    iconName = 'ios-share-outline';
    iconType = 'ionicon';
  }

  return {
    title: `#${requestId}`,
    headerRight: (
      <NavBarIcon iconName={iconName} iconType={iconType} onPress={share} />
    ),
  };
};

FoiRequestDetails.propTypes = {
  navigateToPdfViewer: PropTypes.func.isRequired,
  navigateToPublicBody: PropTypes.func.isRequired,
  request: PropTypes.shape({
    public_body: PropTypes.string.isRequired,
    description: PropTypes.string.isRequired,
    costs: PropTypes.number,
    id: PropTypes.number.isRequired,
    last_message: PropTypes.string,
    first_message: PropTypes.string.isRequired,
    due_date: PropTypes.string.isRequired,
    jurisdiction: PropTypes.string.isRequired,
    law: PropTypes.string.isRequired,
    title: PropTypes.string.isRequired,
    status: PropTypes.string.isRequired,
    resolution: PropTypes.string,
    refusal_reason: PropTypes.string,
    messages: PropTypes.arrayOf(
      PropTypes.shape({
        content: PropTypes.string,
        subject: PropTypes.string.isRequired,
        id: PropTypes.number.isRequired,
        not_publishable: PropTypes.bool.isRequired,
        sender: PropTypes.string.isRequired,
        timestamp: PropTypes.string.isRequired,
        attachments: PropTypes.arrayOf(
          PropTypes.shape({
            id: PropTypes.number.isRequired,
            approved: PropTypes.bool.isRequired,
            filetype: PropTypes.string.isRequired,
            name: PropTypes.string.isRequired,
            site_url: PropTypes.string.isRequired,
          })
        ),
      })
    ).isRequired,
  }).isRequired,
};

export default FoiRequestDetails;
<commit_msg>print public body of escalation<commit_after>import { Button, Divider, Icon } from 'react-native-elements';
import {
  Linking,
  Platform,
  Share,
  Text,
  TouchableHighlight,
  View,
} from 'react-native';
import Accordion from 'react-native-collapsible/Accordion';
import PropTypes from 'prop-types';
import React from 'react';
import moment from 'moment';
import 'moment/locale/de';

import { ORIGIN } from '../../../globals';
import { breakLongWords } from '../../../utils/strings';
import {
  getLawNameAndUrl,
  getPublicBodyNameAndJurisdiction,
} from '../../../utils/fakeApi';
import { getPrintableStatus } from '../../../utils';
import { grey, primaryColor } from '../../../globals/colors';
import { styles } from './styles';
import { styles as tableStyles } from '../Table/styles';
import BlankContainer from '../BlankContainer';
import Heading from '../Heading';
import I18n from '../../../i18n';
import Link from '../Link';
import NavBarIcon from '../../foiRequests/NavBarIcon';
import SubHeading from '../SubHeading';
import Table from '../Table';

class FoiRequestDetails extends React.Component {
  componentDidMount() {
    const locale = I18n.currentLocale().substring(0, 2);
    moment.locale(locale);
  }

  _renderMessageHeader = msg => (
    <View style={[tableStyles.row, styles.msgHeader]}>
      <Text selectable style={[tableStyles.item1, styles.link]}>
        {`${moment(msg.timestamp).format('DD.MM.YYYY')}:`}
      </Text>
      <Text selectable style={[tableStyles.item2, styles.link]}>
        {msg.sender}
      </Text>
    </View>
  );

  _renderAttachments = attachments => {
    return attachments.map(att => {
      const isPdf =
        att.filetype === 'application/pdf' ||
        att.name.toLowerCase().endsWith('.pdf'); // hotfix for #38
      let viewPdfButton;

      if (isPdf) {
        viewPdfButton = (
          <Button
            containerViewStyle={styles.viewPdfButton}
            backgroundColor={primaryColor}
            title={I18n.t('foiRequestDetails.viewPdf')}
            onPress={() => this.props.navigateToPdfViewer({ uri: att.url })}
            icon={{ name: 'remove-red-eye', color: 'white' }}
          />
        );
      }

      return (
        <View key={att.id}>
          <View style={styles.attachmentsRowLabel}>
            <View>
              <Icon name="attach-file" />
            </View>
            <View>
              <Text style={styles.hotfixTextPadding}>
                {att.name.replace(/_|-/g, ' ')}
              </Text>
            </View>
          </View>
          <View style={styles.attachmentsRowButton}>
            <View>
              <Button
                containerViewStyle={styles.downloadButton}
                backgroundColor={primaryColor}
                title={I18n.t('foiRequestDetails.download')}
                icon={{ name: 'file-download', color: 'white' }}
                onPress={() => Linking.openURL(att.url)}
              />
            </View>
            {viewPdfButton}
          </View>
          <Divider style={styles.dividerAttachments} />
        </View>
      );
    });
  };

  _renderMessageContent = msg => {
    let escalation = null;

    if (msg.is_escalation) {
      escalation = (
        <View style={tableStyles.row}>
          <Text style={tableStyles.item1}>
            {I18n.t('foiRequestDetails.esclatedTo')}
          </Text>
          <Text style={tableStyles.item2}>
            {
              getPublicBodyNameAndJurisdiction(msg.recipient_public_body)
                .publicBodyName
            }
          </Text>
        </View>
      );
    }

    return (
      <View style={styles.msgContent}>
        {this._renderAttachments(msg.attachments)}
        <View style={tableStyles.row}>
          <Text style={tableStyles.item1}>
            {I18n.t('foiRequestDetails.from')}
          </Text>
          <Text selectable style={tableStyles.item2}>
            {msg.sender}
          </Text>
        </View>
        <View style={tableStyles.row}>
          <Text style={tableStyles.item1}>
            {I18n.t('foiRequestDetails.on')}
          </Text>
          <Text selectable style={tableStyles.item2}>
            {moment(msg.timestamp).format('LLLL')}
          </Text>
        </View>
        {escalation}
        <View style={tableStyles.row}>
          <Text style={tableStyles.item1}>
            {I18n.t('foiRequestDetails.subject')}
          </Text>
          <Text selectable style={tableStyles.item2}>
            {msg.subject}
          </Text>
        </View>
        <Divider style={styles.dividerBeforeMessageContent} />
        <Text selectable>
          {msg.content_hidden
            ? I18n.t('foiRequestDetails.notYetVisible')
            : msg.content.trim()}
        </Text>
      </View>
    );
  };

  _renderTable = () => {
    const {
      status,
      resolution,
      refusal_reason: refusalReason,
      costs,
      last_message: lastMessage,
      first_message: firstMessage,
      due_date: dueDate,
      law,
    } = this.props.request;

    const { realStatus } = getPrintableStatus(status, resolution);

    const tableData = [
      {
        label: I18n.t('status'),
        value: <Text selectable>{I18n.t(realStatus)}</Text>,
      },
    ];

    if (refusalReason) {
      tableData.push({
        label: I18n.t('foiRequestDetails.refusalReason'),
        value: <Text selectable>{refusalReason}</Text>,
      });
    }

    if (costs && costs !== 0) {
      tableData.push({
        label: I18n.t('foiRequestDetails.costs'),
        value: <Text selectable>{costs}</Text>,
      });
    }

    tableData.push({
      label: I18n.t('foiRequestDetails.startedOn'),
      value: <Text selectable>{moment(firstMessage).format('LLL')}</Text>,
    });

    tableData.push({
      label: I18n.t('foiRequestDetails.lastMessage'),
      value: <Text selectable>{moment(lastMessage).format('LLL')}</Text>,
    });

    if (dueDate) {
      tableData.push({
        label: I18n.t('foiRequestDetails.dueDate'),
        value: <Text selectable>{moment(dueDate).format('LL')}</Text>,
      });
    }

    const { name: lawName, site_url: lawUrl } = getLawNameAndUrl(law);

    if (lawName && lawUrl) {
      tableData.push({
        label: I18n.t('foiRequestDetails.law'),
        value: <Link label={breakLongWords(lawName)} url={lawUrl} />,
      });
    }

    return (
      <View style={styles.table}>

      </View>
    );
  };

  _renderMessages = () => {
    const filtedMessages = this.props.request.messages.filter(
      x => !x.not_publishable
    );
    const messages = filtedMessages.map(
      ({
        id,
        sender,
        subject,
        content,
        timestamp,
        is_response,
        attachments,
        content_hidden,
        is_escalation,
        recipient_public_body,
      }) => {
        const filteredAttachments = attachments
          .filter(x => x.approved)
          .map(x => {
            return {
              key: x.id,
              url: x.site_url,
              name: x.name,
              filetype: x.filetype,
            };
          });
        return {
          key: id,
          sender,
          subject,
          content,
          timestamp,
          content_hidden,
          is_escalation,
          recipient_public_body,
          isRespsone: is_response,
          attachments: filteredAttachments,
        };
      }
    );

    return (
      <View style={styles.msgContainer}>
        <Accordion
          align={'center'}
          duration={1000}
          sections={messages}
          renderHeader={this._renderMessageHeader}
          renderContent={this._renderMessageContent}
          underlayColor={grey}
        />
      </View>
    );
  };

  render() {
    const { title, public_body: publicBody, description } = this.props.request;
    let subheading = (
      <SubHeading style={styles.subheading}>
        {I18n.t('foiRequestDetails.notYetSpecified')}
      </SubHeading>
    );
    if (publicBody) {
      const {
        publicBodyName,
        jurisdictionName,
        publicBodyId,
      } = getPublicBodyNameAndJurisdiction(publicBody);
      subheading = (
        <View>
          <TouchableHighlight
            style={{
              alignSelf: 'center',
            }}
            underlayColor={grey}
            onPress={() => this.props.navigateToPublicBody({ publicBodyId })}
          >
            <View>
              <SubHeading style={[styles.subheading, styles.link]}>
                {publicBodyName}
              </SubHeading>
            </View>
          </TouchableHighlight>
          <Text selectable style={[styles.subheadingJurisdiction]}>
            ({jurisdictionName})
          </Text>
        </View>
      );
    }

    return (
      <BlankContainer>
        <Heading style={styles.heading}>{title}</Heading>
        <View>
          <Text style={styles.subheadingTo}>
            {I18n.t('foiRequestDetails.to')}
          </Text>
          {subheading}
        </View>
        {this._renderTable()}
        <View style={styles.summary}>
          <Text selectable>{description}</Text>
        </View>
        {this._renderMessages()}
      </BlankContainer>
    );
  }
}

FoiRequestDetails.navigationOptions = ({ navigation }) => {
  const requestId = navigation.state.params.request.id;
  function share() {
    Share.share(
      {
        message: 'Check out this Freedom of Information request!',
        url: `${ORIGIN}/a/${requestId}`,
        title: 'FragDenStaat.de',
      },
      {
        // Android only:
        dialogTitle: 'FragDenStaat.de',
      }
    );
  }

  let iconName = 'share';
  let iconType = 'material';

  // platform specific share button
  if (Platform.OS === 'ios') {
    iconName = 'ios-share-outline';
    iconType = 'ionicon';
  }

  return {
    title: `#${requestId}`,
    headerRight: (
      <NavBarIcon iconName={iconName} iconType={iconType} onPress={share} />
    ),
  };
};

FoiRequestDetails.propTypes = {
  navigateToPdfViewer: PropTypes.func.isRequired,
  navigateToPublicBody: PropTypes.func.isRequired,
  request: PropTypes.shape({
    public_body: PropTypes.string.isRequired,
    description: PropTypes.string.isRequired,
    costs: PropTypes.number,
    id: PropTypes.number.isRequired,
    last_message: PropTypes.string,
    first_message: PropTypes.string.isRequired,
    due_date: PropTypes.string.isRequired,
    jurisdiction: PropTypes.string.isRequired,
    law: PropTypes.string.isRequired,
    title: PropTypes.string.isRequired,
    status: PropTypes.string.isRequired,
    resolution: PropTypes.string,
    refusal_reason: PropTypes.string,
    messages: PropTypes.arrayOf(
      PropTypes.shape({
        content: PropTypes.string,
        subject: PropTypes.string.isRequired,
        id: PropTypes.number.isRequired,
        not_publishable: PropTypes.bool.isRequired,
        sender: PropTypes.string.isRequired,
        timestamp: PropTypes.string.isRequired,
        attachments: PropTypes.arrayOf(
          PropTypes.shape({
            id: PropTypes.number.isRequired,
            approved: PropTypes.bool.isRequired,
            filetype: PropTypes.string.isRequired,
            name: PropTypes.string.isRequired,
            site_url: PropTypes.string.isRequired,
          })
        ),
      })
    ).isRequired,
  }).isRequired,
};

export default FoiRequestDetails;
<|endoftext|>


<commit_before>'use strict';

var React = require('react-native');

var {
  ActivityIndicatorIOS,
  AppRegistry,
  AlertIOS,
  Image,
  ListView,
  StyleSheet,
  Text,
  TouchableHighlight,
  View,
  Navigator,
  PixelRatio,
} = React;

var ActionSheetIOS = require('ActionSheetIOS');

var AppReloader = require('NativeModules').AppReloader;
var Api = require("../Api/Core");
var NoResults = require('../Components/NoResults');
var generateAppURL = require('../Utilities/generateAppURL');

var AppList = React.createClass({
  getInitialState() {
    return {
      page: 1,
      data: [],
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
    };
  },

  componentDidMount() {
    this.fetchApps();
  },

  componentDidUpdate: function(prevProps) {
    if (prevProps.url !== this.props.url) {
      this.setState({ loaded: false });
      this.fetchApps();
    }
  },

  fetchApps() {
    if (!this.requestInFlight) {
      this.setState({
        hasError: false
      });

      this.requestInFlight = true;
      var separator = this.props.url.indexOf('?') !== -1 ?
        '&' :
        '?';
      var url = `${this.props.url}${separator}page=${this.state.page}`;

      Api.get(url)
        .then((data) => {
          if (data.error) {
            // TODO: check for 401 Unauthorized
            if (Navigator.getContext(this)) {
              Navigator.getContext(this).replace({id: "login", error: data.error});
            }

            return;
          }

          var page = data.length ?
            this.state.page + 1:
            this.state.page;

          var newData = this.state.data.concat(data);
          this.setState({
            data: newData,
            page,
            dataSource: this.state.dataSource.cloneWithRows(newData),
            loaded: true,
            hasError: false
          });
        })
        .catch((e) => {
          this.setState({hasError: true});
        })
        .finally(() => {
          this.requestInFlight = false;
        })
        .done();

    }
  },

  renderAppList() {
    return (
      <View style={styles.container}>
        <ListView
          dataSource={this.state.dataSource}
          renderRow={this.renderApp}
          initialPageSize={10}
          pageSize={5}
          onEndReachedThreshold={1200}
          onEndReached={this.fetchApps}
          style={styles.listView} />
      </View>
    );
  },

  renderCreator(app) {
    if (app.creator && !this.props.hideCreator) {
      var avatarUrl = 'https://rnplay.org/' + app.creator.avatar_url + "?v=1";

      return (
        <View style={styles.creator}>
          <Image style={styles.avatar} resizeMode="contain" source={{uri: avatarUrl}} />
          <Text style={styles.username} numberOfLines={1}>{app.creator.username || 'guest'}</Text>
        </View>
      )
    }
  },

  shareApp(app) {
    var url = 'https://rnplay.org/plays/' + app.url_token;
    var message = '"' + (app.name || app.module_name) + '" on rnplay.org';

    ActionSheetIOS.showShareActionSheetWithOptions(
      {url: url, message: message},
      (error) => console.log(error),
      (success) => console.log(success)
    );
  },

  renderApp(app) {
    return (
      <View style={{marginBottom: 15}}>
        <TouchableHighlight underlayColor="#F5F5F5" onLongPress={() => this.shareApp(app)}
                                                    onPress={() => this.selectApp(app)}>
          <View style={styles.appContainer}>
            <View style={styles.appTextDescription}>
              <Text style={styles.appTitle} numberOfLines={1}>{app.name || app.module_name}</Text>
              <View style={styles.targetBuild}>
                <Text style={styles.targetBuildText}>Targets <Text>{app.build_name}</Text></Text>
              </View>

              <View style={styles.viewCount}>
                <Text style={styles.viewCountText}>{app.view_count} <Text>views</Text></Text>
              </View>
            </View>

            { this.renderCreator(app) }
          </View>
        </TouchableHighlight>
      </View>
    );
  },

  selectApp(app) {
    AppReloader.reloadAppWithURLString(generateAppURL(app), app.module_name);
  },

  renderLoading() {
    return (
      <View style={styles.loadingContainer}>
        <ActivityIndicatorIOS color={'#712FA9'} style={styles.spinner} size="large" />
      </View>
    );
  },

  renderNoResults() {
    return(
      <NoResults />
    );
  },

  renderRetry() {
    return (
      <View style={styles.retryButtonWrapper}>
        <Image source={require("image!network_error")} style={{opacity: 0.9, marginBottom: 30}} />
        <TouchableHighlight
          style={styles.retryButtonHighlight}
          onPress={() => this.fetchApps()}>
          <View style={styles.retryButtonView}>
            <Text style={styles.retryButtonText}>Connection failed. Retry?</Text>
          </View>
        </TouchableHighlight>
      </View>
    );
  },

  render() {
    if (!this.state.loaded && !this.state.hasError) {
      return this.renderLoading();
    } else if (this.state.hasError && !this.state.data.length) {
      return this.renderRetry();
    } else {
      if (this.state.dataSource.getRowCount() > 0) {
        return this.renderAppList();
      }
      else {
        return this.renderNoResults();
      }
    }
  }
});

var deviceWidth = require('Dimensions').get('window').width;

var styles = StyleSheet.create({
  retryButtonWrapper: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    paddingBottom: 80,
  },

  retryButtonHighlight: {
    borderRadius: 7,
    overflow: 'hidden',
  },

  retryButtonView: {
    height: 40,
    backgroundColor:'#712FA9',
  },

  retryButtonText: {
    padding:10,
    paddingLeft: 20,
    paddingRight: 20,
    color:'#fff',
    textAlign:'center',
    fontWeight:'700'
  },

  listView: {
    marginTop: -9,
    paddingTop: 0,
  },

  container: {
    flex: 1,
    backgroundColor: 'transparent',
  },

  loadingContainer: {
    flex: 1,
    paddingBottom: 80,
  },

  appContainer: {
    overflow: 'hidden',
    marginTop: -18,
    flexDirection: 'row',
    flex: 1,
    paddingBottom: 8,
    paddingTop: 15,
    borderBottomWidth: 3 / PixelRatio.get(),
    borderBottomColor: "#eee"
  },

  appTextDescription: {
    flexDirection: 'column',
    marginLeft: 10,
  },

  targetBuild: {
    opacity: 0.4,
    paddingBottom: 2,
  },

  viewCount: {
    opacity: 0.4,
  },

  targetBuildText: {
    fontSize: 12,
    fontFamily: 'Avenir Next',
  },

  viewCountText: {
    fontSize: 12,
    fontFamily: 'Avenir Next',
  },

  creator: {
    position: 'absolute',
    right: 0,
    width: 70,
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
  },

  username: {
    fontSize: 11,
    opacity: 0.4,
    width: 50,
    textAlign: 'center',
  },

  avatar: {
    width: 30,
    height: 30,
    marginBottom: 5,
    borderRadius: 15,
    backgroundColor: "#000"
  },

  cancelButton: {
    color: '#fff',
    flex: 1,
    fontSize: 25,
    marginLeft: 20
  },

  appTitle: {
    fontSize: 18,
    fontFamily: 'Avenir Next',
    color: '#712FA9',
    width: deviceWidth - 70,
    flex: 1,
  },

  spinner: {
    flex: 1,
  },
});

module.exports = AppList;
<commit_msg>increment hit count when loading an app<commit_after>'use strict';

var React = require('react-native');

var {
  ActivityIndicatorIOS,
  AppRegistry,
  AlertIOS,
  Image,
  ListView,
  StyleSheet,
  Text,
  TouchableHighlight,
  View,
  Navigator,
  PixelRatio,
} = React;

var ActionSheetIOS = require('ActionSheetIOS');

var AppReloader = require('NativeModules').AppReloader;
var Api = require("../Api/Core");
var NoResults = require('../Components/NoResults');
var generateAppURL = require('../Utilities/generateAppURL');

var AppList = React.createClass({
  getInitialState() {
    return {
      page: 1,
      data: [],
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
    };
  },

  componentDidMount() {
    this.fetchApps();
  },

  componentDidUpdate: function(prevProps) {
    if (prevProps.url !== this.props.url) {
      this.setState({ loaded: false });
      this.fetchApps();
    }
  },

  fetchApps() {
    if (!this.requestInFlight) {
      this.setState({
        hasError: false
      });

      this.requestInFlight = true;
      var separator = this.props.url.indexOf('?') !== -1 ?
        '&' :
        '?';
      var url = `${this.props.url}${separator}page=${this.state.page}`;

      Api.get(url)
        .then((data) => {
          if (data.error) {
            // TODO: check for 401 Unauthorized
            if (Navigator.getContext(this)) {
              Navigator.getContext(this).replace({id: "login", error: data.error});
            }

            return;
          }

          var page = data.length ?
            this.state.page + 1:
            this.state.page;

          var newData = this.state.data.concat(data);
          this.setState({
            data: newData,
            page,
            dataSource: this.state.dataSource.cloneWithRows(newData),
            loaded: true,
            hasError: false
          });
        })
        .catch((e) => {
          this.setState({hasError: true});
        })
        .finally(() => {
          this.requestInFlight = false;
        })
        .done();

    }
  },

  renderAppList() {
    return (
      <View style={styles.container}>
        <ListView
          dataSource={this.state.dataSource}
          renderRow={this.renderApp}
          initialPageSize={10}
          pageSize={5}
          onEndReachedThreshold={1200}
          onEndReached={this.fetchApps}
          style={styles.listView} />
      </View>
    );
  },

  renderCreator(app) {
    if (app.creator && !this.props.hideCreator) {
      var avatarUrl = 'https://rnplay.org/' + app.creator.avatar_url + "?v=1";

      return (
        <View style={styles.creator}>
          <Image style={styles.avatar} resizeMode="contain" source={{uri: avatarUrl}} />
          <Text style={styles.username} numberOfLines={1}>{app.creator.username || 'guest'}</Text>
        </View>
      )
    }
  },

  shareApp(app) {
    var url = 'https://rnplay.org/plays/' + app.url_token;
    var message = '"' + (app.name || app.module_name) + '" on rnplay.org';

    ActionSheetIOS.showShareActionSheetWithOptions(
      {url: url, message: message},
      (error) => console.log(error),
      (success) => console.log(success)
    );
  },

  renderApp(app) {
    return (
      <View style={{marginBottom: 15}}>
        <TouchableHighlight underlayColor="#F5F5F5" onLongPress={() => this.shareApp(app)}
                                                    onPress={() => this.selectApp(app)}>
          <View style={styles.appContainer}>
            <View style={styles.appTextDescription}>
              <Text style={styles.appTitle} numberOfLines={1}>{app.name || app.module_name}</Text>
              <View style={styles.targetBuild}>
                <Text style={styles.targetBuildText}>Targets <Text>{app.build_name}</Text></Text>
              </View>

              <View style={styles.viewCount}>
                <Text style={styles.viewCountText}>{app.view_count} <Text>views</Text></Text>
              </View>
            </View>

            { this.renderCreator(app) }
          </View>
        </TouchableHighlight>
      </View>
    );
  },

  selectApp(app) {
    Api.post("/apps/"+app.url_token+"/view.json");
    AppReloader.reloadAppWithURLString(generateAppURL(app), app.module_name);
  },

  renderLoading() {
    return (
      <View style={styles.loadingContainer}>
        <ActivityIndicatorIOS color={'#712FA9'} style={styles.spinner} size="large" />
      </View>
    );
  },

  renderNoResults() {
    return(
      <NoResults />
    );
  },

  renderRetry() {
    return (
      <View style={styles.retryButtonWrapper}>
        <Image source={require("image!network_error")} style={{opacity: 0.9, marginBottom: 30}} />
        <TouchableHighlight
          style={styles.retryButtonHighlight}
          onPress={() => this.fetchApps()}>
          <View style={styles.retryButtonView}>
            <Text style={styles.retryButtonText}>Connection failed. Retry?</Text>
          </View>
        </TouchableHighlight>
      </View>
    );
  },

  render() {
    if (!this.state.loaded && !this.state.hasError) {
      return this.renderLoading();
    } else if (this.state.hasError && !this.state.data.length) {
      return this.renderRetry();
    } else {
      if (this.state.dataSource.getRowCount() > 0) {
        return this.renderAppList();
      }
      else {
        return this.renderNoResults();
      }
    }
  }
});

var deviceWidth = require('Dimensions').get('window').width;

var styles = StyleSheet.create({
  retryButtonWrapper: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    paddingBottom: 80,
  },

  retryButtonHighlight: {
    borderRadius: 7,
    overflow: 'hidden',
  },

  retryButtonView: {
    height: 40,
    backgroundColor:'#712FA9',
  },

  retryButtonText: {
    padding:10,
    paddingLeft: 20,
    paddingRight: 20,
    color:'#fff',
    textAlign:'center',
    fontWeight:'700'
  },

  listView: {
    marginTop: -9,
    paddingTop: 0,
  },

  container: {
    flex: 1,
    backgroundColor: 'transparent',
  },

  loadingContainer: {
    flex: 1,
    paddingBottom: 80,
  },

  appContainer: {
    overflow: 'hidden',
    marginTop: -18,
    flexDirection: 'row',
    flex: 1,
    paddingBottom: 8,
    paddingTop: 15,
    borderBottomWidth: 3 / PixelRatio.get(),
    borderBottomColor: "#eee"
  },

  appTextDescription: {
    flexDirection: 'column',
    marginLeft: 10,
  },

  targetBuild: {
    opacity: 0.4,
    paddingBottom: 2,
  },

  viewCount: {
    opacity: 0.4,
  },

  targetBuildText: {
    fontSize: 12,
    fontFamily: 'Avenir Next',
  },

  viewCountText: {
    fontSize: 12,
    fontFamily: 'Avenir Next',
  },

  creator: {
    position: 'absolute',
    right: 0,
    width: 70,
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
  },

  username: {
    fontSize: 11,
    opacity: 0.4,
    width: 50,
    textAlign: 'center',
  },

  avatar: {
    width: 30,
    height: 30,
    marginBottom: 5,
    borderRadius: 15,
    backgroundColor: "#000"
  },

  cancelButton: {
    color: '#fff',
    flex: 1,
    fontSize: 25,
    marginLeft: 20
  },

  appTitle: {
    fontSize: 18,
    fontFamily: 'Avenir Next',
    color: '#712FA9',
    width: deviceWidth - 70,
    flex: 1,
  },

  spinner: {
    flex: 1,
  },
});

module.exports = AppList;
<|endoftext|>


<commit_before>'use strict';

/**
 * Configuration options for {@link Mongod}.
 * @typedef {Object} Mongod~Config
 * @property {String} [bin=mongod]
 * @property {String} [config]
 * @property {(Number|String)} [port=27017]
 * @property {String} [dbpath]
 * @property {String} [storageEngine]
 * @property {Boolean} [nojournal=false]
 */

/**
 * Invoked when an operation (i.e. {@link Mongod#open}) completes.
 * @callback Mongod~callback
 * @argument {Error} err
 */

const childprocess = require('child_process');
const events = require('events');
const PromiseQueue = require('promise-queue');

/**
 * A collection of regualar expressions used by {@link Mongod.parseData} to
 * parse stdout and stderr messages.
 * @see Mongod.parseData
 * @readonly
 * @private
 * @type {Object.<String,RegExp>}
 */
const regExp = {
  terminalMessage: /waiting\s+for\s+connections|already\s+in\s+use|denied|error|exception|badvalue/im,
  whiteSpace: /\s/g,
  newline: /\r?\n/
};

/**
 * Start and stop a local MongoDB server like a boss.
 * @class
 */
class Mongod extends events.EventEmitter {

  /**
   * Get a function that takes chunks of stdin data, aggregates it, and passes
   * it in complete lines, one by one, to a given {@link Mongod~callback}.
   * @argument {Mongod~callback} callback
   * @return {Function}
   */
  static getTextLineAggregator(callback) {
    let buffer = '';

    return (data) => {
      const fragments = data.toString().split(regExp.newline);
      const lines = fragments.slice(0, fragments.length - 1);

      // If there was an unended line in the previous dump, complete it by
      // the first section.
      lines[0] = buffer + lines[0];

      // If there is an unended line in this dump, store it to be completed by
      // the next. This assumes there will be a terminating newline character
      // at some point. Generally, this is a safe assumption.
      buffer = fragments[fragments.length - 1];

      for (let line of lines) {
        callback(line);
      }
    };
  }

  /**
   * Populate a given {@link Mongod~Config} with values from a
   * given {@link Mongod~Config}.
   * @protected
   * @argument {Mongod~Config} source
   * @argument {Mongod~Config} target
   * @return {Mongod~Config}
   */
  static parseConfig(source, target) {
    if (target == null) {
      target = Object.create(null);
    }

    if (source == null) {
      return target;
    }

    if (typeof source === 'number' || typeof source === 'string') {
      target.port = source;

      return target;
    }

    if (typeof source !== 'object') {
      return target;
    }

    if (source.bin != null) {
      target.bin = source.bin;
    }

    if (source.conf != null) {
      target.conf = source.conf;

      return target;
    }

    if (source.nojournal === true) {
      target.nojournal = true;
    }

    if (source.storageEngine != null) {
      target.storageEngine = source.storageEngine;
    }

    if (source.dbpath != null) {
      target.dbpath = source.dbpath;
    }

    if (source.port != null) {
      target.port = source.port;
    }

    return target;
  }

  /**
   * Parse process flags for MongoDB from a given {@link Mongod~Config}.
   * @protected
   * @argument {Mongod~Config} config
   * @return {Array.<String>}
   */
  static parseFlags(config) {
    if (config.conf != null) {
      return ['--config', config.conf];
    }

    const flags = [];

    if (config.nojournal) {
      flags.push('--nojournal');
    }

    if (config.storageEngine != null) {
      flags.push('--storageEngine', config.storageEngine);
    }

    if (config.dbpath != null) {
      flags.push('--dbpath', config.dbpath);
    }

    if (config.port != null) {
      flags.push('--port', config.port);
    }

    return flags;
  }

  /**
   * Parse MongoDB server output for terminal messages.
   * @protected
   * @argument {String} string
   * @return {Object}
   */
  static parseData(string) {
    const matches = regExp.terminalMessage.exec(string);

    if (matches === null) {
      return null;
    }

    const result = {
      err: null,
      key: matches
      .pop()
      .replace(regExp.whiteSpace, '')
      .toLowerCase()
    };

    switch (result.key) {
      case 'waitingforconnections':
        break;

      case 'alreadyinuse':
        result.err = new Error('Address already in use');
        result.err.code = -1;

        break;

      case 'denied':
        result.err = new Error('Permission denied');
        result.err.code = -2;

        break;

      case 'error':
      case 'exception':
      case 'badvalue':
        result.err = new Error(string.trim());
        result.err.code = -3;

        break;
    }

    return result;
  }

  /**
   * Start a given {@link Mongod}.
   * @protected
   * @argument {Mongod} server
   * @return {Promise}
   */
  static open(server) {
    if (server.isOpening) {
      return server.openPromise;
    }

    server.isOpening = true;
    server.isClosing = false;
    server.openPromise = server.promiseQueue.add(() => {
      if (server.isClosing || server.isRunning) {
        server.isOpening = false;

        return Promise.resolve(null);
      }

      return new Promise((resolve, reject) => {
        /**
         * A listener for the current server process' stdout/stderr that
         * resolves or rejects the current {@link Promise} when done.
         * @see Mongod.getTextLineAggregator
         * @see Mongod.parseData
         * @argument {Buffer} buffer
         * @return {undefined}
         */
        const dataListener = Mongod.getTextLineAggregator((value) => {
          const result = Mongod.parseData(value);

          if (result === null) {
            return;
          }

          server.process.stdout.removeListener('data', dataListener);

          server.isOpening = false;

          if (result.err === null) {
            server.isRunning = true;

            server.emit('open');
            resolve(null);
          }
          else {
            server.isClosing = true;

            server.emit('closing');
            server.process.once('close', () => reject(result.err));
          }
        });

        /**
         * A listener to close the server when the current process exits.
         * @return {undefined}
         */
        const exitListener = () => {
          // istanbul ignore next
          server.close();
        };

        /**
         * Get a text line aggregator that emits a given {@linkcode event}
         * for the current server.
         * @see Mongod.getTextLineAggregator
         * @argument {String} event
         * @return {Function}
         */
        const getDataPropagator = (event) =>
          Mongod.getTextLineAggregator((line) => server.emit(event, line));

        server.emit('opening');

        server.process = childprocess.spawn(
          server.config.bin,
          Mongod.parseFlags(server.config)
        );

        server.process.stderr.on('data', dataListener);
        server.process.stderr.on('data', getDataPropagator('stdout'));
        server.process.stdout.on('data', dataListener);
        server.process.stdout.on('data', getDataPropagator('stdout'));
        server.process.on('close', () => {
          server.process = null;
          server.isRunning = false;
          server.isClosing = false;

          process.removeListener('exit', exitListener);
          server.emit('close');
        });
        process.on('exit', exitListener);
      });
    });

    return server.openPromise;
  }

  /**
   * Stop a given {@link Mongod}.
   * @protected
   * @argument {Mongod} server
   * @return {Promise}
   */
  static close(server) {
    if (server.isClosing) {
      return server.closePromise;
    }

    server.isClosing = true;
    server.isOpening = false;
    server.closePromise = server.promiseQueue.add(() => {
      if (server.isOpening || !server.isRunning) {
        server.isClosing = false;

        return Promise.resolve(null);
      }

      return new Promise((resolve) => {
        server.emit('closing');
        server.process.once('close', () => resolve(null));
        server.process.kill();
      });
    });

    return server.closePromise;
  }

  /**
   * Construct a new {@link Mongod}.
   * @argument {(Number|String|Mongod~Config)} [configOrPort]
   * A number or string that is a port or an object for configuration.
   */
  constructor(configOrPort) {
    super();

    /**
     * Configuration options.
     * @protected
     * @type {Mongod~Config}
     */
    this.config = Mongod.parseConfig(configOrPort, {
      bin: 'mongod',
      conf: null,
      port: 27017,
      dbpath: null,
      storageEngine: null,
      nojournal: false
    });

    /**
     * The current process.
     * @protected
     * @type {ChildProcess}
     */
    this.process = null;

    /**
     * The last {@link Promise} returned by {@link Mongod#open}.
     * @protected
     * @type {Promise}
     */
    this.openPromise = Promise.resolve(null);

    /**
     * The last {@link Promise} returned by {@link Mongod#close}.
     * @protected
     * @type {Promise}
     */
    this.closePromise = Promise.resolve(null);

    /**
     * A serial queue of open and close promises.
     * @protected
     * @type {PromiseQueue}
     */
    this.promiseQueue = new PromiseQueue(1);

    /**
     * Determine if the instance is closing a MongoDB server; {@linkcode true}
     * while a process is being, or about to be, killed until the
     * contained MongoDB server either closes or errs.
     * @readonly
     * @type {Boolean}
     */
    this.isClosing = false;

    /**
     * Determine if the instance is starting a MongoDB server; {@linkcode true}
     * while a process is spawning, or about tobe spawned, until the
     * contained MongoDB server either starts or errs.
     * @readonly
     * @type {Boolean}
     */
    this.isRunning = false;

    /**
     * Determine if the instance is running a MongoDB server; {@linkcode true}
     * once a process has spawned and the contained MongoDB server is ready
     * to service requests.
     * @readonly
     * @type {Boolean}
     */
    this.isOpening = false;
  }

  /**
   * Open the server.
   * @argument {Mongod~callback} [callback]
   * @return {Promise}
   */
  open(callback) {
    const promise = Mongod.open(this);

    return typeof callback === 'function'
    ? promise
      .then((v) => callback(null, v))
      .catch((e) => callback(e, null))
    : promise;
  }

  /**
   * Close the server.
   * @argument {Mongod~callback} [callback]
   * @return {Promise}
   */
  close(callback) {
    const promise = Mongod.close(this);

    return typeof callback === 'function'
    ? promise
      .then((v) => callback(null, v))
      .catch((e) => callback(e, null))
    : promise;
  }
}

module.exports = exports = Mongod;
<commit_msg>Document events<commit_after>'use strict';

/**
 * Configuration options for {@link Mongod}.
 * @typedef {Object} Mongod~Config
 * @property {String} [bin=mongod]
 * @property {String} [config]
 * @property {(Number|String)} [port=27017]
 * @property {String} [dbpath]
 * @property {String} [storageEngine]
 * @property {Boolean} [nojournal=false]
 */

/**
 * Invoked when an operation (i.e. {@link Mongod#open}) completes.
 * @callback Mongod~callback
 * @argument {Error} err
 */

/**
 * Emitted when a MongoDB server prints to stdout.
 * @event Mongod#stdout
 */

/**
 * Emitted when attempting to start a MongoDB server.
 * @event Mongod#opening
 */

/**
 * Emitted when a MongoDB server becomes ready to service requests.
 * @event Mongod#open
 */

/**
 * Emitted when attempting to stop a MongoDB server.
 * @event Mongod#closing
 */

/**
 * Emitted once a MongoDB server has stopped.
 * @event Mongod#close
 */

const childprocess = require('child_process');
const events = require('events');
const PromiseQueue = require('promise-queue');

/**
 * A collection of regualar expressions used by {@link Mongod.parseData} to
 * parse stdout and stderr messages.
 * @see Mongod.parseData
 * @readonly
 * @private
 * @type {Object.<String,RegExp>}
 */
const regExp = {
  terminalMessage: /waiting\s+for\s+connections|already\s+in\s+use|denied|error|exception|badvalue/im,
  whiteSpace: /\s/g,
  newline: /\r?\n/
};

/**
 * Start and stop a local MongoDB server like a boss.
 * @class
 */
class Mongod extends events.EventEmitter {

  /**
   * Get a function that takes chunks of stdin data, aggregates it, and passes
   * it in complete lines, one by one, to a given {@link Mongod~callback}.
   * @argument {Mongod~callback} callback
   * @return {Function}
   */
  static getTextLineAggregator(callback) {
    let buffer = '';

    return (data) => {
      const fragments = data.toString().split(regExp.newline);
      const lines = fragments.slice(0, fragments.length - 1);

      // If there was an unended line in the previous dump, complete it by
      // the first section.
      lines[0] = buffer + lines[0];

      // If there is an unended line in this dump, store it to be completed by
      // the next. This assumes there will be a terminating newline character
      // at some point. Generally, this is a safe assumption.
      buffer = fragments[fragments.length - 1];

      for (let line of lines) {
        callback(line);
      }
    };
  }

  /**
   * Populate a given {@link Mongod~Config} with values from a
   * given {@link Mongod~Config}.
   * @protected
   * @argument {Mongod~Config} source
   * @argument {Mongod~Config} target
   * @return {Mongod~Config}
   */
  static parseConfig(source, target) {
    if (target == null) {
      target = Object.create(null);
    }

    if (source == null) {
      return target;
    }

    if (typeof source === 'number' || typeof source === 'string') {
      target.port = source;

      return target;
    }

    if (typeof source !== 'object') {
      return target;
    }

    if (source.bin != null) {
      target.bin = source.bin;
    }

    if (source.conf != null) {
      target.conf = source.conf;

      return target;
    }

    if (source.nojournal === true) {
      target.nojournal = true;
    }

    if (source.storageEngine != null) {
      target.storageEngine = source.storageEngine;
    }

    if (source.dbpath != null) {
      target.dbpath = source.dbpath;
    }

    if (source.port != null) {
      target.port = source.port;
    }

    return target;
  }

  /**
   * Parse process flags for MongoDB from a given {@link Mongod~Config}.
   * @protected
   * @argument {Mongod~Config} config
   * @return {Array.<String>}
   */
  static parseFlags(config) {
    if (config.conf != null) {
      return ['--config', config.conf];
    }

    const flags = [];

    if (config.nojournal) {
      flags.push('--nojournal');
    }

    if (config.storageEngine != null) {
      flags.push('--storageEngine', config.storageEngine);
    }

    if (config.dbpath != null) {
      flags.push('--dbpath', config.dbpath);
    }

    if (config.port != null) {
      flags.push('--port', config.port);
    }

    return flags;
  }

  /**
   * Parse MongoDB server output for terminal messages.
   * @protected
   * @argument {String} string
   * @return {Object}
   */
  static parseData(string) {
    const matches = regExp.terminalMessage.exec(string);

    if (matches === null) {
      return null;
    }

    const result = {
      err: null,
      key: matches
        .pop()
        .replace(regExp.whiteSpace, '')
        .toLowerCase()
    };

    switch (result.key) {
      case 'waitingforconnections':
        break;

      case 'alreadyinuse':
        result.err = new Error('Address already in use');
        result.err.code = -1;

        break;

      case 'denied':
        result.err = new Error('Permission denied');
        result.err.code = -2;

        break;

      case 'error':
      case 'exception':
      case 'badvalue':
        result.err = new Error(string.trim());
        result.err.code = -3;

        break;
    }

    return result;
  }

  /**
   * Start a given {@link Mongod}.
   * @protected
   * @argument {Mongod} server
   * @return {Promise}
   */
  static open(server) {
    if (server.isOpening) {
      return server.openPromise;
    }

    server.isOpening = true;
    server.isClosing = false;
    server.openPromise = server.promiseQueue.add(() => {
      if (server.isClosing || server.isRunning) {
        server.isOpening = false;

        return Promise.resolve(null);
      }

      return new Promise((resolve, reject) => {
        /**
         * A listener for the current server process' stdout/stderr that
         * resolves or rejects the current {@link Promise} when done.
         * @see Mongod.getTextLineAggregator
         * @see Mongod.parseData
         * @argument {Buffer} buffer
         * @return {undefined}
         */
        const dataListener = Mongod.getTextLineAggregator((value) => {
          const result = Mongod.parseData(value);

          if (result === null) {
            return;
          }

          server.process.stdout.removeListener('data', dataListener);

          server.isOpening = false;

          if (result.err === null) {
            server.isRunning = true;

            server.emit('open');
            resolve(null);
          }
          else {
            server.isClosing = true;

            server.emit('closing');
            server.process.once('close', () => reject(result.err));
          }
        });

        /**
         * A listener to close the server when the current process exits.
         * @return {undefined}
         */
        const exitListener = () => {
          // istanbul ignore next
          server.close();
        };

        /**
         * Get a text line aggregator that emits a given {@linkcode event}
         * for the current server.
         * @see Mongod.getTextLineAggregator
         * @argument {String} event
         * @return {Function}
         */
        const getDataPropagator = (event) =>
          Mongod.getTextLineAggregator((line) => server.emit(event, line));

        server.emit('opening');

        server.process = childprocess.spawn(
          server.config.bin,
          Mongod.parseFlags(server.config)
        );

        server.process.stderr.on('data', dataListener);
        server.process.stderr.on('data', getDataPropagator('stdout'));
        server.process.stdout.on('data', dataListener);
        server.process.stdout.on('data', getDataPropagator('stdout'));
        server.process.on('close', () => {
          server.process = null;
          server.isRunning = false;
          server.isClosing = false;

          process.removeListener('exit', exitListener);
          server.emit('close');
        });
        process.on('exit', exitListener);
      });
    });

    return server.openPromise;
  }

  /**
   * Stop a given {@link Mongod}.
   * @protected
   * @argument {Mongod} server
   * @return {Promise}
   */
  static close(server) {
    if (server.isClosing) {
      return server.closePromise;
    }

    server.isClosing = true;
    server.isOpening = false;
    server.closePromise = server.promiseQueue.add(() => {
      if (server.isOpening || !server.isRunning) {
        server.isClosing = false;

        return Promise.resolve(null);
      }

      return new Promise((resolve) => {
        server.emit('closing');
        server.process.once('close', () => resolve(null));
        server.process.kill();
      });
    });

    return server.closePromise;
  }

  /**
   * Construct a new {@link Mongod}.
   * @argument {(Number|String|Mongod~Config)} [configOrPort]
   * A number or string that is a port or an object for configuration.
   */
  constructor(configOrPort) {
    super();

    /**
     * Configuration options.
     * @protected
     * @type {Mongod~Config}
     */
    this.config = Mongod.parseConfig(configOrPort, {
      bin: 'mongod',
      conf: null,
      port: 27017,
      dbpath: null,
      storageEngine: null,
      nojournal: false
    });

    /**
     * The current process.
     * @protected
     * @type {ChildProcess}
     */
    this.process = null;

    /**
     * The last {@link Promise} returned by {@link Mongod#open}.
     * @protected
     * @type {Promise}
     */
    this.openPromise = Promise.resolve(null);

    /**
     * The last {@link Promise} returned by {@link Mongod#close}.
     * @protected
     * @type {Promise}
     */
    this.closePromise = Promise.resolve(null);

    /**
     * A serial queue of open and close promises.
     * @protected
     * @type {PromiseQueue}
     */
    this.promiseQueue = new PromiseQueue(1);

    /**
     * Determine if the instance is closing a MongoDB server; {@linkcode true}
     * while a process is being, or about to be, killed until the
     * contained MongoDB server either closes or errs.
     * @readonly
     * @type {Boolean}
     */
    this.isClosing = false;

    /**
     * Determine if the instance is starting a MongoDB server; {@linkcode true}
     * while a process is spawning, or about tobe spawned, until the
     * contained MongoDB server either starts or errs.
     * @readonly
     * @type {Boolean}
     */
    this.isRunning = false;

    /**
     * Determine if the instance is running a MongoDB server; {@linkcode true}
     * once a process has spawned and the contained MongoDB server is ready
     * to service requests.
     * @readonly
     * @type {Boolean}
     */
    this.isOpening = false;
  }

  /**
   * Open the server.
   * @argument {Mongod~callback} [callback]
   * @return {Promise}
   */
  open(callback) {
    const promise = Mongod.open(this);

    if (typeof callback === 'function') {
      return promise
        .then((v) => callback(null, v))
        .catch((e) => callback(e, null));
    }

    return promise;
  }

  /**
   * Close the server.
   * @argument {Mongod~callback} [callback]
   * @return {Promise}
   */
  close(callback) {
    const promise = Mongod.close(this);

    if (typeof callback === 'function') {
      return promise
        .then((v) => callback(null, v))
        .catch((e) => callback(e, null));
    }

    return promise;
  }
}

module.exports = exports = Mongod;
<|endoftext|>


<commit_before>Mosque = function(game, x, y, resource) {
	this.type = "Mosque";
	Building.call(this, game, x, y, resource);
};

Mosque.prototype = Object.create(Building.prototype);
Mosque.prototype.constructor = Mosque;

Mosque.prototype.sendWaves = function() {
	console.log("MOSQUE SENDING WAVES");
	var smallWave = this.game.make.sprite(this.width/2, 0, "mosque_wave_small");
	var bigWave = this.game.make.sprite(this.width/2, 0, "mosque_wave_big");
    smallWave.anchor.setTo(0.5, 0.5);
    smallWave.scale.setTo(0.0, 0.0);
    this.addChild(smallWave);
    var tween = this.game.add.tween(smallWave.scale);
    tween.to({ x: 1.0, y: 1.0 }, 100);
    tween.start();
};

<commit_msg>wave animation<commit_after>Mosque = function(game, x, y, resource) {
	this.type = "Mosque";
	Building.call(this, game, x, y, resource);
};

Mosque.prototype = Object.create(Building.prototype);
Mosque.prototype.constructor = Mosque;

Mosque.prototype.sendWaves = function() {
	console.log("MOSQUE SENDING WAVES");
	var smallWave = this.game.make.sprite(this.width/2, 0, "mosque_wave_small");
	var bigWave = this.game.make.sprite(this.width/2, 0, "mosque_wave_big");
	bigWave.anchor.setTo(0.5, 0.5);
	smallWave.anchor.setTo(0.5, 0.5);

    var bigWaveAnim = this.createWaveAnimation(bigWave);
    var smallWaveAnim = this.createWaveAnimation(smallWave);
    bigWaveAnim.start();
    setInterval(function() {
    	smallWaveAnim.start();
    }, 160);

};

Mosque.prototype.createWaveAnimation = function(waveEntity) {
	waveEntity.scale.setTo(0.0, 0.0);
	this.addChild(waveEntity);
	var anim = this.game.add.tween(waveEntity.scale);
	anim.to({ x: 1.0, y: 1.0 }, 1000);
	var loopCount = 3;
	var mosque = this;

	var animationStarted = function() {
    	waveEntity.scale.setTo(0.0, 0.0);
    };
	var animationStopped = function() {
    	mosque.removeChild(waveEntity);
    };
    var animationLooped = function() {
    	loopCount = Math.max(0, loopCount - 1);
    	if (loopCount == 0) {
    		anim.loop = false;
    	}
    }
    anim.onStart.add(animationStarted, this);
    anim.onLoop.add(animationLooped, this);
    anim.onComplete.add(animationStopped, this);
    anim.loop = true;
    return anim;

}

<|endoftext|>


<commit_before>import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { ScrollContainer } from 'react-router-scroll';
import PropTypes from 'prop-types';
import StatusContainer from '../containers/status_container';
import LoadMore from './load_more';
import ImmutablePureComponent from 'react-immutable-pure-component';
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';

class StatusList extends ImmutablePureComponent {

  static propTypes = {
    scrollKey: PropTypes.string.isRequired,
    statusIds: ImmutablePropTypes.list.isRequired,
    onScrollToBottom: PropTypes.func,
    onScrollToTop: PropTypes.func,
    onScroll: PropTypes.func,
    trackScroll: PropTypes.bool,
    shouldUpdateScroll: PropTypes.func,
    isLoading: PropTypes.bool,
    hasMore: PropTypes.bool,
    prepend: PropTypes.node,
    emptyMessage: PropTypes.node,
  };

  static defaultProps = {
    trackScroll: true,
  };

  intersectionObserverWrapper = new IntersectionObserverWrapper();

  handleScroll = (e) => {
    const { scrollTop, scrollHeight, clientHeight } = e.target;
    const offset = scrollHeight - scrollTop - clientHeight;
    this._oldScrollPosition = scrollHeight - scrollTop;

    if (250 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
      this.props.onScrollToBottom();
    } else if (scrollTop < 100 && this.props.onScrollToTop) {
      this.props.onScrollToTop();
    } else if (this.props.onScroll) {
      this.props.onScroll();
    }
  }

  componentDidMount () {
    this.attachScrollListener();
    this.attachIntersectionObserver();
  }

  componentDidUpdate (prevProps) {
    if ((prevProps.statusIds.size < this.props.statusIds.size && prevProps.statusIds.first() !== this.props.statusIds.first() && !!this._oldScrollPosition) && this.node.scrollTop > 0) {
      this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition;
    }
  }

  componentWillUnmount () {
    this.detachScrollListener();
    this.detachIntersectionObserver();
  }

  attachIntersectionObserver () {
    this.intersectionObserverWrapper.connect({
      root: this.node,
      rootMargin: '300% 0px',
    });
  }

  detachIntersectionObserver () {
    this.intersectionObserverWrapper.disconnect();
  }

  attachScrollListener () {
    this.node.addEventListener('scroll', this.handleScroll);
  }

  detachScrollListener () {
    this.node.removeEventListener('scroll', this.handleScroll);
  }

  setRef = (c) => {
    this.node = c;
  }

  handleLoadMore = (e) => {
    e.preventDefault();
    this.props.onScrollToBottom();
  }

  render () {
    const { statusIds, onScrollToBottom, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;

    let loadMore       = null;
    let scrollableArea = null;

    if (!isLoading && statusIds.size > 0 && hasMore) {
      loadMore = <LoadMore onClick={this.handleLoadMore} />;
    }

    if (isLoading || statusIds.size > 0 || !emptyMessage) {
      scrollableArea = (

            {prepend}

            {statusIds.map((statusId) => {
              return <StatusContainer key={statusId} id={statusId} intersectionObserverWrapper={this.intersectionObserverWrapper} />;
            })}

            {loadMore}

      );
    } else {
      scrollableArea = (

          {emptyMessage}

      );
    }

    if (trackScroll) {
      return (
        <ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}>
          {scrollableArea}
        </ScrollContainer>
      );
    } else {
      return scrollableArea;
    }
  }

}

export default StatusList;
<commit_msg>Fix jittery scrolling for Chromium browsers (#3776) (#3832)<commit_after>import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { ScrollContainer } from 'react-router-scroll';
import PropTypes from 'prop-types';
import StatusContainer from '../containers/status_container';
import LoadMore from './load_more';
import ImmutablePureComponent from 'react-immutable-pure-component';
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
import { debounce } from 'lodash';

class StatusList extends ImmutablePureComponent {

  static propTypes = {
    scrollKey: PropTypes.string.isRequired,
    statusIds: ImmutablePropTypes.list.isRequired,
    onScrollToBottom: PropTypes.func,
    onScrollToTop: PropTypes.func,
    onScroll: PropTypes.func,
    trackScroll: PropTypes.bool,
    shouldUpdateScroll: PropTypes.func,
    isLoading: PropTypes.bool,
    hasMore: PropTypes.bool,
    prepend: PropTypes.node,
    emptyMessage: PropTypes.node,
  };

  static defaultProps = {
    trackScroll: true,
  };

  intersectionObserverWrapper = new IntersectionObserverWrapper();

  handleScroll = debounce((e) => {
    const { scrollTop, scrollHeight, clientHeight } = e.target;
    const offset = scrollHeight - scrollTop - clientHeight;
    this._oldScrollPosition = scrollHeight - scrollTop;

    if (250 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
      this.props.onScrollToBottom();
    } else if (scrollTop < 100 && this.props.onScrollToTop) {
      this.props.onScrollToTop();
    } else if (this.props.onScroll) {
      this.props.onScroll();
    }
  }, 200, {
    trailing: true,
  });

  componentDidMount () {
    this.attachScrollListener();
    this.attachIntersectionObserver();
  }

  componentDidUpdate (prevProps) {
    // Reset the scroll position when a new toot comes in in order not to
    // jerk the scrollbar around if you're already scrolled down the page.
    if (prevProps.statusIds.size < this.props.statusIds.size &&
        prevProps.statusIds.first() !== this.props.statusIds.first() &&
        this._oldScrollPosition &&
        this.node.scrollTop > 0) {
      let newScrollTop = this.node.scrollHeight - this._oldScrollPosition;
      if (this.node.scrollTop !== newScrollTop) {
        this.node.scrollTop = newScrollTop;
      }
    }
  }

  componentWillUnmount () {
    this.detachScrollListener();
    this.detachIntersectionObserver();
  }

  attachIntersectionObserver () {
    this.intersectionObserverWrapper.connect({
      root: this.node,
      rootMargin: '300% 0px',
    });
  }

  detachIntersectionObserver () {
    this.intersectionObserverWrapper.disconnect();
  }

  attachScrollListener () {
    this.node.addEventListener('scroll', this.handleScroll);
  }

  detachScrollListener () {
    this.node.removeEventListener('scroll', this.handleScroll);
  }

  setRef = (c) => {
    this.node = c;
  }

  handleLoadMore = (e) => {
    e.preventDefault();
    this.props.onScrollToBottom();
  }

  render () {
    const { statusIds, onScrollToBottom, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;

    let loadMore       = null;
    let scrollableArea = null;

    if (!isLoading && statusIds.size > 0 && hasMore) {
      loadMore = <LoadMore onClick={this.handleLoadMore} />;
    }

    if (isLoading || statusIds.size > 0 || !emptyMessage) {
      scrollableArea = (

            {prepend}

            {statusIds.map((statusId) => {
              return <StatusContainer key={statusId} id={statusId} intersectionObserverWrapper={this.intersectionObserverWrapper} />;
            })}

            {loadMore}

      );
    } else {
      scrollableArea = (

          {emptyMessage}

      );
    }

    if (trackScroll) {
      return (
        <ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}>
          {scrollableArea}
        </ScrollContainer>
      );
    } else {
      return scrollableArea;
    }
  }

}

export default StatusList;
<|endoftext|>


<commit_before>'use strict';

module.exports = {
    "recipe": {
		"type": "string", "required": true,
	},
	"gitSource": {
		"type": "object",
		"required": true,
		"properties": {
			"owner": {"required": true, "type": "string"},
			"repo": {"required": true, "type": "string"},
			"branch": {"required": true, "type": "string", "minLength": 1, 'pattern': /[a-z]+/},
			"commit": {"required": false, "type": "string"}
		}
	},
	"deployConfig": {
		"type": "object",
		"required": true,
		"properties": {
			"memoryLimit": {"required": false, "type": "number", "default": 209715200},
			"cpuLimit": {"required": false, "type": "string"},
			"isKubernetes": {"required": false, "type": "boolean"}, //NOTE: only required in case of controller deployment
			"replication": {
				"required": true,
				"type": "object",
				"properties": {
					"mode": {
						"required": true,
						"type": "string",
						"enum": ['replicated', 'global', 'deployment', 'daemonset']
					},
					"replicas": {"required": false, "type": "number"}
				}
			}
		}
	},
	"autoScale": {
		"type": "object",
		"required": false,
		"properties": {
			"replicas": {
				"required": true,
				"type": "object",
				"properties": {
					"min": {"required": true, "type": "integer", "min": 1},
					"max": {"required": true, "type": "integer", "min": 1}
				},
				"additionalProperties": false
			},
			"metrics": {
				"required": true,
				"type": "object",
				"properties": {
					"cpu": {
						"required": true,
						"type": "object",
						"properties": {
							"percent": {"required": true, "type": "number"}
						},
						"additionalProperties": false
					}
				},
				"additionalProperties": false
			}
		},
		"additionalProperties": false
	},
	"custom": {
		"type": "object",
		"required": false,
		"properties": {
			"image": {
				"type": "object",
				"required": false,
				"properties": {
					"prefix": {"required": false, "type": "string"},
					"name": {"required": false, "type": "string"},
					"tag": {"required": false, "type": "string"},
				}
			},
			"env": {
				"type": "object",
				"required": false,
				"additionalProperties": {"type": "string"}
			},
			"type": {
				"required": true,
				"type": "string"
			},
			"name": {
				"required": false,
				"type": "string"
			},
			"version": {
				"required": false,
				"type": "string"
			},
			"daemonGroup": {
				"required": false,
				"type": "string"
			},
			"gc": {
				"required": false,
				"type": "object",
				"properties": {
					"gcName": {"required": true, "type": "string"},
					"gcVersion": {"required": true, "type": "number"}
				}
			}
		}
	}
};
<commit_msg>removed unneeded pattern<commit_after>'use strict';

module.exports = {
    "recipe": {
		"type": "string", "required": true,
	},
	"gitSource": {
		"type": "object",
		"required": true,
		"properties": {
			"owner": {"required": true, "type": "string"},
			"repo": {"required": true, "type": "string"},
			"branch": {"required": true, "type": "string", "minLength": 1},
			"commit": {"required": false, "type": "string"}
		}
	},
	"deployConfig": {
		"type": "object",
		"required": true,
		"properties": {
			"memoryLimit": {"required": false, "type": "number", "default": 209715200},
			"cpuLimit": {"required": false, "type": "string"},
			"isKubernetes": {"required": false, "type": "boolean"}, //NOTE: only required in case of controller deployment
			"replication": {
				"required": true,
				"type": "object",
				"properties": {
					"mode": {
						"required": true,
						"type": "string",
						"enum": ['replicated', 'global', 'deployment', 'daemonset']
					},
					"replicas": {"required": false, "type": "number"}
				}
			}
		}
	},
	"autoScale": {
		"type": "object",
		"required": false,
		"properties": {
			"replicas": {
				"required": true,
				"type": "object",
				"properties": {
					"min": {"required": true, "type": "integer", "min": 1},
					"max": {"required": true, "type": "integer", "min": 1}
				},
				"additionalProperties": false
			},
			"metrics": {
				"required": true,
				"type": "object",
				"properties": {
					"cpu": {
						"required": true,
						"type": "object",
						"properties": {
							"percent": {"required": true, "type": "number"}
						},
						"additionalProperties": false
					}
				},
				"additionalProperties": false
			}
		},
		"additionalProperties": false
	},
	"custom": {
		"type": "object",
		"required": false,
		"properties": {
			"image": {
				"type": "object",
				"required": false,
				"properties": {
					"prefix": {"required": false, "type": "string"},
					"name": {"required": false, "type": "string"},
					"tag": {"required": false, "type": "string"},
				}
			},
			"env": {
				"type": "object",
				"required": false,
				"additionalProperties": {"type": "string"}
			},
			"type": {
				"required": true,
				"type": "string"
			},
			"name": {
				"required": false,
				"type": "string"
			},
			"version": {
				"required": false,
				"type": "string"
			},
			"daemonGroup": {
				"required": false,
				"type": "string"
			},
			"gc": {
				"required": false,
				"type": "object",
				"properties": {
					"gcName": {"required": true, "type": "string"},
					"gcVersion": {"required": true, "type": "number"}
				}
			}
		}
	}
};
<|endoftext|>


<commit_before><commit_msg>"apply" arguments to new view instead of "call"<commit_after><|endoftext|>


<commit_before>var OAuth = Package.oauth.OAuth;

/*
  Registers the oauth service.
 */
OAuth.registerService('spotify', 2, null, function(query) {

});

/*
  Helper function that returns an object with:
    accessToken (token itself)
    expiresIn (token lifespan)
 */
var getTokens = function(query) {

};

// Helper function that fetches and returns the user's Spotify profile.
var getIdentity = function(accessToken) {

}

Spotify.retrieveCredential = function(credentialToken, credentialSecret) {
  return OAuth.retrieveCredential(credentialToken, credentialSecret);
};<commit_msg>work on server<commit_after>var OAuth = Package.oauth.OAuth;

Spotify.whitelistedFields = ['display_name', 'email', 'id', 'uri', 'images'];

/*
  Registers the oauth service.
 */
OAuth.registerService('spotify', 2, null, function(query) {
  var response = getTokens(query);
  var refreshToken = response.refreshToken;
  var identity = getIdentity(accessToken);

  // Set the service data.
  var serviceData = {
    accessToken: response.accessToken,
    expiresAt: (+new Date) + (1000 * response.expiresIn)
  };

  // Set refresh token.
  if (refreshToken) {
    serviceData.refreshToken = refreshToken;
  }

  // Set any additional fields.
  var fields = _.pick(identity, Spotify.whitelistedFields);
  _.extend(serviceData, fields);

  return {
    serviceData: serviceData,
    options: { profile: fields }
  };
});

// checks whether a string parses as JSON
var isJSON = function (str) {
  try {
    JSON.parse(str);
    return true;
  } catch (e) {
    return false;
  }
};

/*
  Helper function that returns an object with:
    accessToken (token itself)
    expiresIn (token lifespan)
    refreshToken
 */
var getTokens = function(query) {
  var config = ServiceConfiguration.configurations.findOne({service: 'spotify'});
  if (!config)
    throw new ServiceConfiguration.ConfigError();

  var response;
  try {
    // Request access token.
    response = HTTP.post(
      "https://accounts.spotify.com/api/token", { params: {
        code: query.code,
        client_id: config.clientId,
        client_secret: OAuth.openSecret(config.secret),
        redirect_uri: OAuth._redirectUri('spotify', config),
        grant_type: 'authorization_code'
      }});
  } catch (err) {
    throw _.extend(new Error("Failed to complete OAuth handshake with Spotify. " + err.message), {response: err.response});
  }

  // Spotify returns responses like 'E{"error":"server_error","error_description":"Unexpected status: 415"}' on error.
  if (isJSON(response)) {
    throw new Error("Failed to complete OAuth handshake with Spotify. " + response);
  } else if (!response.data.access_token) {
    throw new Error("Failed to complete OAuth handshake with Spotify. No access_token found in response.");
  } else {
    return {
      accessToken: response.data.access_token,
      refreshToken: response.data.refresh_token,
      expiresIn: response.data.expires_in
    };
  }
};

// Helper function that fetches and returns the user's Spotify profile.
var getIdentity = function(accessToken) {
  try {
    return HTTP.get(
      "https://api.spotify.com/v1/me",
      { params: { access_token: accessToken }}).data;
  } catch (err) {
    throw _.extend(new Error("Failed to fetch identity from Spotify. " + err.message), { response: err.response });
  }
}

Spotify.retrieveCredential = function(credentialToken, credentialSecret) {
  return OAuth.retrieveCredential(credentialToken, credentialSecret);
};<|endoftext|>


<commit_before>
let tests = [
    {
        text: "hello world",
        key: "obfuscated",
        expected: "nxn7RojhaY4ZncPnqYJsPA=="
    },
    {
        text: "this is a nice plant pot",
        key: "obfuscated",
        expected: "7vgFBFGRjhQ3JprrfyQ/d5VoI7QH4Qaf0Rfo3w=="
    },
    {
        text: "abc def",
        key: "obfuscated",
        expected: "AB9FOVMJockDQLX4"
    },
    {
        text: "Chupa chups candy canes marshmallow donut donut jelly-o pudding halvah dessert. Oat cake marshmallow lollipop. Toffee chocolate jujubes cheesecake caramels toffee ice cream jujubes.",
        key: "obfuscated",
        expected: "yZaeElo0wscVDe6lGWZt+VJ/G+wkKKSTHSlR8VFhoa7NFEm8LzlNtp+q0+UjckIRBdyzLy3eoYU4AhoSGaebhCF/NycwfsRFhcy1/FRl2fM9wpJfTysb9SyIc2Mcj7YqahZUBXWFEVYxG4yhiQ4lNj+fRMVKNwuNiEJn79UlPSnHioqOTOqm3TBzXhC5xM1uk+gcVRyHv0XagwQ4IaXaxnuArcLZavYHV2O1XBrjnpmQM1oUei/9RaSM0AM="
    },
    {
        text: "最健不問動栃人点掲条写覇学球太一惑千収意。連降日限極目音雅左正著者績来幕原越氏",
        key: "obfuscated",
        expected: "I+3ThHyHXrNiYSQGTVsXzQD0AT3v6p6eJGPvjljTVp2pTIMTikf1+szVbiifD5lBRyHdZouNH5nylS8ikKu4XMv4FtxNOJEWliMNeQtEDWBB1eM9kXUL3g8fQPUx1upci5bUqKbshiDOwjnjd0BwInBGGTVL3grnw7Jd2w=="
    },
    {
        text: "Οι περισσότεροι δεν ζητούν από τον σωστό άνθρωπο να δει το σημείο να γράφει υψηλό βαθμό σύγχυσης",
        key: "obfuscated",
        expected: "DkSfaTHtmFg7pWhyKdg9UiiF23iZ+WxiR6W7WQTO1+FHdOEgHbEICIrG+QGncE4b9njppSwvQpRYKkVTPRYezCCWwoVRrE1sRfc3ZoK9fgsmRgNKXiksZkWueKpWf5Bi9UxSiObsEZZ32SxVMlr05S2ySISWsG8ciO9Hc7+FT8napvZUG8vWQCLQ5GIxtLAleaoVOGcCW7ZcsQXdtXWgJfyplAJ76JslBZ5G0KjNSf6YMica"
    },
    {
        text: "Большинство не спрашивают у правого человека, чтобы понять, как писать высокую степень замешательства",
        key: "obfuscated",
        expected: "zIumsRhj1YPHuq2d0R1kEz2AAph1TVGN3e6CdTTeopBZCPr0rp8g6AxaJQokJ+5nZURQ5tpYK0HdatqnDqyKLJiYQIhu7Guj0DUI3b0vMh7DOXyr5bjxwrs0ttmMJdqxXxlm0VJZ6f1zyKUm0ljOYEx0eqf23N2YKvYX4Z4LCb6cbGyqq8UKo7uHNo5yCGhSlHjKszAP9OzS6bk/Y2CDqwXDrSK8MBHtcYD8CRFq/vxdrbU47c2Ocf4TdpY3FPCJ"
    },
    {
        text: "hello world",
        key: "a_very_long_obfuscated_password_for_testing",
        expected: "9NGLtdIrvC7QbBjbzpxjtg=="
    },
    {
        text: "this is a nice plant pot",
        key: "a_very_long_obfuscated_password_for_testing",
        expected: "zDdTfHednHL8LFCZVrshYE/mC1+UbPqsJbRLSA=="
    },
    {
        text: "abc def",
        key: "a_very_long_obfuscated_password_for_testing",
        expected: "Ufbl7myGuNfrrQOZ"
    },
    {
        text: "Chupa chups candy canes marshmallow donut donut jelly-o pudding halvah dessert. Oat cake marshmallow lollipop. Toffee chocolate jujubes cheesecake caramels toffee ice cream jujubes.",
        key: "a_very_long_obfuscated_password_for_testing",
        expected: "CbMbUTv7ZVFplVf9BTZe93uC+ReN3fc0N/XQ1NZ8j2IR1iUAAkZLQogtAU1XvOzUV9vClpnDnXwE15GlWJjGac+Keo3fazJOmh2t9TIdI6+qVVkNR5pczNOavvpeCbK7Sz6JyKNsbOMJkMqcz7kTQRlhwuia2ZlA0bji62d5fZrhDNerP/03o/EobV1kOSkW+tAOkCJ41RbA08a0nFGSleBlI0M7NdVjz72QlD1NCGb2atLljOCDQ0+WggA="
    },
    {
        text: "最健不問動栃人点掲条写覇学球太一惑千収意。連降日限極目音雅左正著者績来幕原越氏",
        key: "a_very_long_obfuscated_password_for_testing",
        expected: "i0hoJUJBQvgaIYrRBvyDek8mLeo5P6jDzY6fjOFkKbFZtEq1vQMs+g5OOWQZ091IDgfgHpmRNpu4Z83JZ8x7XuQvSIQgF4INNz9xcHWxdN1b4KERQR+tma3ccS1ZCHy2e6f7fGzqAo3h5Lf2QTQubMfp8O4I51Kyh8mhiQ=="
    },
    {
        text: "Οι περισσότεροι δεν ζητούν από τον σωστό άνθρωπο να δει το σημείο να γράφει υψηλό βαθμό σύγχυσης",
        key: "a_very_long_obfuscated_password_for_testing",
        expected: "7J3pzVEFAbAPRWrRJoAjxyHciQfEdNO+xXe+5ANFa+tABDYNfe5pzq63vTHuCGUJt2TxgphlQ/DiEZ5lKSoMOspGI4LawJe6UFhUDXZ7scpYRxnJ8fRZUyv9zseSCUPGlseDdH4Xpv3Fhm0YDhKpDOI/Sr3rTvnbh7I6Ytdlbjav85Chw82esSU+poNUVHHBjh1SYzQrEcQ8/VQI97Zu6pv+KAU87gRLEqNyRwHJKtQKZlfH"
    },
    {
        text: "Большинство не спрашивают у правого человека, чтобы понять, как писать высокую степень замешательства",
        key: "obfuscated",
        expected: "zIumsRhj1YPHuq2d0R1kEz2AAph1TVGN3e6CdTTeopBZCPr0rp8g6AxaJQokJ+5nZURQ5tpYK0HdatqnDqyKLJiYQIhu7Guj0DUI3b0vMh7DOXyr5bjxwrs0ttmMJdqxXxlm0VJZ6f1zyKUm0ljOYEx0eqf23N2YKvYX4Z4LCb6cbGyqq8UKo7uHNo5yCGhSlHjKszAP9OzS6bk/Y2CDqwXDrSK8MBHtcYD8CRFq/vxdrbU47c2Ocf4TdpY3FPCJ"
    },
];

const tea = require("./tea");

let failed = false;

for (let testcase of tests) {
    let encoded = tea.encode(testcase.text, testcase.key);

    if (encoded !== testcase.expected) {
        console.log(`Encoding failed:\n\tGot:      ${encoded}\n\tExpected: ${testcase.expected}\n\tText:     ${testcase.text}\n`);
        failed = true;
    } else {
        let decoded = tea.decode(encoded, testcase.key);

        if (decoded !== testcase.text) {
            console.log(`Decoding failed:\n\tGot:      ${decoded}\n\tExpected: ${testcase.text}\n`);
            failed = true;
        }
    }
}

if (!failed)
    console.log("All tests successful");

process.exit(failed ? 1 : 0);
<commit_msg>Replace let with var in tests<commit_after>
var tests = [
    {
        text: "hello world",
        key: "obfuscated",
        expected: "nxn7RojhaY4ZncPnqYJsPA=="
    },
    {
        text: "this is a nice plant pot",
        key: "obfuscated",
        expected: "7vgFBFGRjhQ3JprrfyQ/d5VoI7QH4Qaf0Rfo3w=="
    },
    {
        text: "abc def",
        key: "obfuscated",
        expected: "AB9FOVMJockDQLX4"
    },
    {
        text: "Chupa chups candy canes marshmallow donut donut jelly-o pudding halvah dessert. Oat cake marshmallow lollipop. Toffee chocolate jujubes cheesecake caramels toffee ice cream jujubes.",
        key: "obfuscated",
        expected: "yZaeElo0wscVDe6lGWZt+VJ/G+wkKKSTHSlR8VFhoa7NFEm8LzlNtp+q0+UjckIRBdyzLy3eoYU4AhoSGaebhCF/NycwfsRFhcy1/FRl2fM9wpJfTysb9SyIc2Mcj7YqahZUBXWFEVYxG4yhiQ4lNj+fRMVKNwuNiEJn79UlPSnHioqOTOqm3TBzXhC5xM1uk+gcVRyHv0XagwQ4IaXaxnuArcLZavYHV2O1XBrjnpmQM1oUei/9RaSM0AM="
    },
    {
        text: "最健不問動栃人点掲条写覇学球太一惑千収意。連降日限極目音雅左正著者績来幕原越氏",
        key: "obfuscated",
        expected: "I+3ThHyHXrNiYSQGTVsXzQD0AT3v6p6eJGPvjljTVp2pTIMTikf1+szVbiifD5lBRyHdZouNH5nylS8ikKu4XMv4FtxNOJEWliMNeQtEDWBB1eM9kXUL3g8fQPUx1upci5bUqKbshiDOwjnjd0BwInBGGTVL3grnw7Jd2w=="
    },
    {
        text: "Οι περισσότεροι δεν ζητούν από τον σωστό άνθρωπο να δει το σημείο να γράφει υψηλό βαθμό σύγχυσης",
        key: "obfuscated",
        expected: "DkSfaTHtmFg7pWhyKdg9UiiF23iZ+WxiR6W7WQTO1+FHdOEgHbEICIrG+QGncE4b9njppSwvQpRYKkVTPRYezCCWwoVRrE1sRfc3ZoK9fgsmRgNKXiksZkWueKpWf5Bi9UxSiObsEZZ32SxVMlr05S2ySISWsG8ciO9Hc7+FT8napvZUG8vWQCLQ5GIxtLAleaoVOGcCW7ZcsQXdtXWgJfyplAJ76JslBZ5G0KjNSf6YMica"
    },
    {
        text: "Большинство не спрашивают у правого человека, чтобы понять, как писать высокую степень замешательства",
        key: "obfuscated",
        expected: "zIumsRhj1YPHuq2d0R1kEz2AAph1TVGN3e6CdTTeopBZCPr0rp8g6AxaJQokJ+5nZURQ5tpYK0HdatqnDqyKLJiYQIhu7Guj0DUI3b0vMh7DOXyr5bjxwrs0ttmMJdqxXxlm0VJZ6f1zyKUm0ljOYEx0eqf23N2YKvYX4Z4LCb6cbGyqq8UKo7uHNo5yCGhSlHjKszAP9OzS6bk/Y2CDqwXDrSK8MBHtcYD8CRFq/vxdrbU47c2Ocf4TdpY3FPCJ"
    },
    {
        text: "hello world",
        key: "a_very_long_obfuscated_password_for_testing",
        expected: "9NGLtdIrvC7QbBjbzpxjtg=="
    },
    {
        text: "this is a nice plant pot",
        key: "a_very_long_obfuscated_password_for_testing",
        expected: "zDdTfHednHL8LFCZVrshYE/mC1+UbPqsJbRLSA=="
    },
    {
        text: "abc def",
        key: "a_very_long_obfuscated_password_for_testing",
        expected: "Ufbl7myGuNfrrQOZ"
    },
    {
        text: "Chupa chups candy canes marshmallow donut donut jelly-o pudding halvah dessert. Oat cake marshmallow lollipop. Toffee chocolate jujubes cheesecake caramels toffee ice cream jujubes.",
        key: "a_very_long_obfuscated_password_for_testing",
        expected: "CbMbUTv7ZVFplVf9BTZe93uC+ReN3fc0N/XQ1NZ8j2IR1iUAAkZLQogtAU1XvOzUV9vClpnDnXwE15GlWJjGac+Keo3fazJOmh2t9TIdI6+qVVkNR5pczNOavvpeCbK7Sz6JyKNsbOMJkMqcz7kTQRlhwuia2ZlA0bji62d5fZrhDNerP/03o/EobV1kOSkW+tAOkCJ41RbA08a0nFGSleBlI0M7NdVjz72QlD1NCGb2atLljOCDQ0+WggA="
    },
    {
        text: "最健不問動栃人点掲条写覇学球太一惑千収意。連降日限極目音雅左正著者績来幕原越氏",
        key: "a_very_long_obfuscated_password_for_testing",
        expected: "i0hoJUJBQvgaIYrRBvyDek8mLeo5P6jDzY6fjOFkKbFZtEq1vQMs+g5OOWQZ091IDgfgHpmRNpu4Z83JZ8x7XuQvSIQgF4INNz9xcHWxdN1b4KERQR+tma3ccS1ZCHy2e6f7fGzqAo3h5Lf2QTQubMfp8O4I51Kyh8mhiQ=="
    },
    {
        text: "Οι περισσότεροι δεν ζητούν από τον σωστό άνθρωπο να δει το σημείο να γράφει υψηλό βαθμό σύγχυσης",
        key: "a_very_long_obfuscated_password_for_testing",
        expected: "7J3pzVEFAbAPRWrRJoAjxyHciQfEdNO+xXe+5ANFa+tABDYNfe5pzq63vTHuCGUJt2TxgphlQ/DiEZ5lKSoMOspGI4LawJe6UFhUDXZ7scpYRxnJ8fRZUyv9zseSCUPGlseDdH4Xpv3Fhm0YDhKpDOI/Sr3rTvnbh7I6Ytdlbjav85Chw82esSU+poNUVHHBjh1SYzQrEcQ8/VQI97Zu6pv+KAU87gRLEqNyRwHJKtQKZlfH"
    },
    {
        text: "Большинство не спрашивают у правого человека, чтобы понять, как писать высокую степень замешательства",
        key: "obfuscated",
        expected: "zIumsRhj1YPHuq2d0R1kEz2AAph1TVGN3e6CdTTeopBZCPr0rp8g6AxaJQokJ+5nZURQ5tpYK0HdatqnDqyKLJiYQIhu7Guj0DUI3b0vMh7DOXyr5bjxwrs0ttmMJdqxXxlm0VJZ6f1zyKUm0ljOYEx0eqf23N2YKvYX4Z4LCb6cbGyqq8UKo7uHNo5yCGhSlHjKszAP9OzS6bk/Y2CDqwXDrSK8MBHtcYD8CRFq/vxdrbU47c2Ocf4TdpY3FPCJ"
    },
];

const tea = require("./tea");

var failed = false;

for (var testcase of tests) {
    var encoded = tea.encode(testcase.text, testcase.key);

    if (encoded !== testcase.expected) {
        console.log(`Encoding failed:\n\tGot:      ${encoded}\n\tExpected: ${testcase.expected}\n\tText:     ${testcase.text}\n`);
        failed = true;
    } else {
        var decoded = tea.decode(encoded, testcase.key);

        if (decoded !== testcase.text) {
            console.log(`Decoding failed:\n\tGot:      ${decoded}\n\tExpected: ${testcase.text}\n`);
            failed = true;
        }
    }
}

if (!failed)
    console.log("All tests successful");

process.exit(failed ? 1 : 0);
<|endoftext|>