<commit_before>/**
 * Monitor
 * Pokemon Showdown - http://pokemonshowdown.com/
 *
 * Various utility functions to make sure PS is running healthily.
 *
 * @license MIT license
 */
'use strict';

const FS = require('./lib/fs');

const MONITOR_CLEAN_TIMEOUT = 2 * 60 * 60 * 1000;

/**
 * This counts the number of times an action has been committed, and tracks the
 * delta of time since the last time it was committed. Actions include
 * connecting to the server, starting a battle, validating a team, and
 * sending/receiving data over a connection's socket.
 * @augments {Map<string, [number, number]>}
 */
// @ts-ignore TypeScript bug
class TimedCounter extends Map {
	/**
	 * Increments the number of times an action has been committed by one, and
	 * updates the delta of time since it was last committed.
	 *
	 * @param {string} key
	 * @param {number} timeLimit
	 * @return {[number, number]} - [action count, time delta]
	 */
	increment(key, timeLimit) {
		let val = this.get(key);
		let now = Date.now();
		if (!val || now > val[1] + timeLimit) {
			this.set(key, [1, Date.now()]);
			return [1, 0];
		} else {
			val[0]++;
			return [val[0], now - val[1]];
		}
	}
}

// Config.loglevel is:
// 0 = everything
// 1 = debug (same as 0 for now)
// 2 = notice (default)
// 3 = warning
// (4 is currently unused)
// 5 = supposedly completely silent, but for now a lot of PS output doesn't respect loglevel
if (('Config' in global) &&
		(typeof Config.loglevel !== 'number' || Config.loglevel < 0 || Config.loglevel > 5)) {
	Config.loglevel = 2;
}

// @ts-ignore
const Monitor = module.exports = {
	/*********************************************************
	 * Logging
	 *********************************************************/

	/** @param {Error} error */
	crashlog(error, source = 'The main process') {
		require('./lib/crashlogger')(error, source);
	},

	/**
	 * @param {string} text
	 */
	log(text) {
		this.notice(text);
		if (Rooms('staff')) {
			Rooms('staff').add(`|c|~|${text}`).update();
		}
	},

	/**
	 * @param {string} text
	 */
	adminlog(text) {
		this.notice(text);
		if (Rooms('upperstaff')) {
			Rooms('upperstaff').add(`|c|~|${text}`).update();
		}
	},

	/**
	 * @param {string} text
	 */
	logHTML(text) {
		this.notice(text);
		if (Rooms('staff')) {
			Rooms('staff').add(`|html|${text}`).update();
		}
	},

	/**
	 * @param {string} text
	 */
	debug(text) {
		if (Config.loglevel <= 1) console.log(text);
	},

	/**
	 * @param {string} text
	 */
	warn(text) {
		if (Config.loglevel <= 3) console.log(text);
	},

	/**
	 * @param {string} text
	 */
	notice(text) {
		if (Config.loglevel <= 2) console.log(text);
	},

	/*********************************************************
	 * Resource Monitor
	 *********************************************************/

	clean() {
		this.clearNetworkUse();
		this.battlePreps.clear();
		this.battles.clear();
		this.connections.clear();
		Dnsbl.cache.clear();
	},

	connections: new TimedCounter(),
	battles: new TimedCounter(),
	battlePreps: new TimedCounter(),
	groupChats: new TimedCounter(),
	tickets: new TimedCounter(),

	/** @type {string | null} */
	activeIp: null,
	networkUse: {},
	networkCount: {},
	hotpatchLock: {},

	/**
	 * Counts a connection. Returns true if the connection should be terminated for abuse.
	 *
	 * @param {string} ip
	 * @param {string} [name = '']
	 * @return {boolean}
	 */
	countConnection(ip, name = '') {
		let [count, duration] = this.connections.increment(ip, 30 * 60 * 1000);
		if (count === 500) {
			this.adminlog(`[ResourceMonitor] IP ${ip} banned for cflooding (${count} times in ${Chat.toDurationString(duration)}${name ? ': ' + name : ''})`);
			return true;
		}

		if (count > 500) {
			if (count % 500 === 0) {
				let c = count / 500;
				if (c === 2 || c === 4 || c === 10 || c === 20 || c % 40 === 0) {
					this.adminlog(`[ResourceMonitor] IP ${ip} still cflooding (${count} times in ${Chat.toDurationString(duration)}${name ? ': ' + name : ''})`);
				}
			}
			return true;
		}

		return false;
	},

	/**
	 * Counts battles created. Returns true if the connection should be
	 * terminated for abuse.
	 *
	 * @param {string} ip
	 * @param {string} [name = '']
	 * @return {boolean}
	 */
	countBattle(ip, name = '') {
		let [count, duration] = this.battles.increment(ip, 30 * 60 * 1000);
		if (duration < 5 * 60 * 1000 && count % 30 === 0) {
			this.adminlog(`[ResourceMonitor] IP ${ip} has battled ${count} times in the last ${Chat.toDurationString(duration)}${name ? ': ' + name : ''})`);
			return true;
		}

		if (count % 150 === 0) {
			this.adminlog(`[ResourceMonitor] IP ${ip} has battled ${count} times in the last ${Chat.toDurationString(duration)}${name ? ': ' + name : ''}`);
			return true;
		}

		return false;
	},

	/**
	 * Counts team validations. Returns true if too many.
	 *
	 * @param {string} ip
	 * @param {Connection} connection
	 * @return {boolean}
	 */
	countPrepBattle(ip, connection) {
		let count = this.battlePreps.increment(ip, 3 * 60 * 1000)[0];
		if (count <= 12) return false;
		if (count < 120 && Punishments.sharedIps.has(ip)) return false;
		connection.popup('Due to high load, you are limited to 12 battles and team validations every 3 minutes.');
		return true;
	},

	/**
	 * Counts concurrent battles. Returns true if too many.
	 *
	 * @param {number} count
	 * @param {Connection} connection
	 * @return {boolean}
	 */
	countConcurrentBattle(count, connection) {
		if (count <= 5) return false;
		connection.popup(`Due to high load, you are limited to 5 games at the same time.`);
		return true;
	},
	/**
	 * Counts group chat creation. Returns true if too much.
	 *
	 * @param {string} ip
	 * @return {boolean}
	 */
	countGroupChat(ip) {
		let count = this.groupChats.increment(ip, 60 * 60 * 1000)[0];
		return count > 4;
	},

	/**
	 * Counts ticket creation. Returns true if too much.
	 *
	 * @param {string} ip
	 * @return {boolean}
	 */
	countTickets(ip) {
		let count = this.tickets.increment(ip, 60 * 60 * 1000)[0];
		if (Punishments.sharedIps.has(ip) && count >= 50) return true;
		if (count >= 5) return true;
		return false;
	},

	/**
	 * Counts the data length received by the last connection to send a
	 * message, as well as the data length in the server's response.
	 *
	 * @param {number} size
	 */
	countNetworkUse(size) {
		if (!Config.emergency || typeof this.activeIp !== 'string') return;
		// @ts-ignore
		if (this.activeIp in this.networkUse) {
			// @ts-ignore
			this.networkUse[this.activeIp] += size;
			// @ts-ignore
			this.networkCount[this.activeIp]++;
		} else {
			// @ts-ignore
			this.networkUse[this.activeIp] = size;
			// @ts-ignore
			this.networkCount[this.activeIp] = 1;
		}
	},

	writeNetworkUse() {
		let buf = '';
		for (let i in this.networkUse) {
			buf += `${this.networkUse[i]}\t${this.networkCount[i]}\t${i}\n`;
		}
		FS('logs/networkuse.tsv').write(buf);
	},

	clearNetworkUse() {
		if (Config.emergency) {
			this.networkUse = {};
			this.networkCount = {};
		}
	},

	/**
	 * Counts roughly the size of an object to have an idea of the server load.
	 *
	 * @param {any} object
	 * @return {number}
	 */
	sizeOfObject(object) {
		/** @type {Set<(Array | Object)>} */
		let objectCache = new Set();
		let stack = [object];
		let bytes = 0;

		while (stack.length) {
			let value = stack.pop();
			switch (typeof value) {
			case 'boolean':
				bytes += 4;
				break;
			case 'string':
				bytes += value.length * 2;
				break;
			case 'number':
				bytes += 8;
				break;
			case 'object':
				if (!objectCache.has(value)) objectCache.add(value);
				if (Array.isArray(value)) {
					for (let el of value) stack.push(el);
				} else {
					for (let i in value) stack.push(value[i]);
				}
				break;
			}
		}

		return bytes;
	},

	/** @type {{new(entries: [any, [number, number]]): TimedCounter}} */
	TimedCounter,
};

Monitor.cleanInterval = setInterval(() => Monitor.clean(), MONITOR_CLEAN_TIMEOUT);
<commit_msg>Fix some outdated // @ts-ignore uses<commit_after>/**
 * Monitor
 * Pokemon Showdown - http://pokemonshowdown.com/
 *
 * Various utility functions to make sure PS is running healthily.
 *
 * @license MIT license
 */
'use strict';

const FS = require('./lib/fs');

const MONITOR_CLEAN_TIMEOUT = 2 * 60 * 60 * 1000;

/**
 * This counts the number of times an action has been committed, and tracks the
 * delta of time since the last time it was committed. Actions include
 * connecting to the server, starting a battle, validating a team, and
 * sending/receiving data over a connection's socket.
 * @augments {Map<string, [number, number]>}
 */
// @ts-ignore TypeScript bug
class TimedCounter extends Map {
	/**
	 * Increments the number of times an action has been committed by one, and
	 * updates the delta of time since it was last committed.
	 *
	 * @param {string} key
	 * @param {number} timeLimit
	 * @return {[number, number]} - [action count, time delta]
	 */
	increment(key, timeLimit) {
		let val = this.get(key);
		let now = Date.now();
		if (!val || now > val[1] + timeLimit) {
			this.set(key, [1, Date.now()]);
			return [1, 0];
		} else {
			val[0]++;
			return [val[0], now - val[1]];
		}
	}
}

// Config.loglevel is:
// 0 = everything
// 1 = debug (same as 0 for now)
// 2 = notice (default)
// 3 = warning
// (4 is currently unused)
// 5 = supposedly completely silent, but for now a lot of PS output doesn't respect loglevel
if (('Config' in global) &&
		(typeof Config.loglevel !== 'number' || Config.loglevel < 0 || Config.loglevel > 5)) {
	Config.loglevel = 2;
}

// @ts-ignore
const Monitor = module.exports = {
	/*********************************************************
	 * Logging
	 *********************************************************/

	/** @param {Error} error */
	crashlog(error, source = 'The main process') {
		require('./lib/crashlogger')(error, source);
	},

	/**
	 * @param {string} text
	 */
	log(text) {
		this.notice(text);
		if (Rooms('staff')) {
			Rooms('staff').add(`|c|~|${text}`).update();
		}
	},

	/**
	 * @param {string} text
	 */
	adminlog(text) {
		this.notice(text);
		if (Rooms('upperstaff')) {
			Rooms('upperstaff').add(`|c|~|${text}`).update();
		}
	},

	/**
	 * @param {string} text
	 */
	logHTML(text) {
		this.notice(text);
		if (Rooms('staff')) {
			Rooms('staff').add(`|html|${text}`).update();
		}
	},

	/**
	 * @param {string} text
	 */
	debug(text) {
		if (Config.loglevel <= 1) console.log(text);
	},

	/**
	 * @param {string} text
	 */
	warn(text) {
		if (Config.loglevel <= 3) console.log(text);
	},

	/**
	 * @param {string} text
	 */
	notice(text) {
		if (Config.loglevel <= 2) console.log(text);
	},

	/*********************************************************
	 * Resource Monitor
	 *********************************************************/

	clean() {
		this.clearNetworkUse();
		this.battlePreps.clear();
		this.battles.clear();
		this.connections.clear();
		Dnsbl.cache.clear();
	},

	connections: new TimedCounter(),
	battles: new TimedCounter(),
	battlePreps: new TimedCounter(),
	groupChats: new TimedCounter(),
	tickets: new TimedCounter(),

	/** @type {string | null} */
	activeIp: null,
	networkUse: {},
	networkCount: {},
	hotpatchLock: {},

	/**
	 * Counts a connection. Returns true if the connection should be terminated for abuse.
	 *
	 * @param {string} ip
	 * @param {string} [name = '']
	 * @return {boolean}
	 */
	countConnection(ip, name = '') {
		let [count, duration] = this.connections.increment(ip, 30 * 60 * 1000);
		if (count === 500) {
			this.adminlog(`[ResourceMonitor] IP ${ip} banned for cflooding (${count} times in ${Chat.toDurationString(duration)}${name ? ': ' + name : ''})`);
			return true;
		}

		if (count > 500) {
			if (count % 500 === 0) {
				let c = count / 500;
				if (c === 2 || c === 4 || c === 10 || c === 20 || c % 40 === 0) {
					this.adminlog(`[ResourceMonitor] IP ${ip} still cflooding (${count} times in ${Chat.toDurationString(duration)}${name ? ': ' + name : ''})`);
				}
			}
			return true;
		}

		return false;
	},

	/**
	 * Counts battles created. Returns true if the connection should be
	 * terminated for abuse.
	 *
	 * @param {string} ip
	 * @param {string} [name = '']
	 * @return {boolean}
	 */
	countBattle(ip, name = '') {
		let [count, duration] = this.battles.increment(ip, 30 * 60 * 1000);
		if (duration < 5 * 60 * 1000 && count % 30 === 0) {
			this.adminlog(`[ResourceMonitor] IP ${ip} has battled ${count} times in the last ${Chat.toDurationString(duration)}${name ? ': ' + name : ''})`);
			return true;
		}

		if (count % 150 === 0) {
			this.adminlog(`[ResourceMonitor] IP ${ip} has battled ${count} times in the last ${Chat.toDurationString(duration)}${name ? ': ' + name : ''}`);
			return true;
		}

		return false;
	},

	/**
	 * Counts team validations. Returns true if too many.
	 *
	 * @param {string} ip
	 * @param {Connection} connection
	 * @return {boolean}
	 */
	countPrepBattle(ip, connection) {
		let count = this.battlePreps.increment(ip, 3 * 60 * 1000)[0];
		if (count <= 12) return false;
		if (count < 120 && Punishments.sharedIps.has(ip)) return false;
		connection.popup('Due to high load, you are limited to 12 battles and team validations every 3 minutes.');
		return true;
	},

	/**
	 * Counts concurrent battles. Returns true if too many.
	 *
	 * @param {number} count
	 * @param {Connection} connection
	 * @return {boolean}
	 */
	countConcurrentBattle(count, connection) {
		if (count <= 5) return false;
		connection.popup(`Due to high load, you are limited to 5 games at the same time.`);
		return true;
	},
	/**
	 * Counts group chat creation. Returns true if too much.
	 *
	 * @param {string} ip
	 * @return {boolean}
	 */
	countGroupChat(ip) {
		let count = this.groupChats.increment(ip, 60 * 60 * 1000)[0];
		return count > 4;
	},

	/**
	 * Counts ticket creation. Returns true if too much.
	 *
	 * @param {string} ip
	 * @return {boolean}
	 */
	countTickets(ip) {
		let count = this.tickets.increment(ip, 60 * 60 * 1000)[0];
		if (Punishments.sharedIps.has(ip) && count >= 50) return true;
		if (count >= 5) return true;
		return false;
	},

	/**
	 * Counts the data length received by the last connection to send a
	 * message, as well as the data length in the server's response.
	 *
	 * @param {number} size
	 */
	countNetworkUse(size) {
		if (!Config.emergency || typeof this.activeIp !== 'string') return;
		if (this.activeIp in this.networkUse) {
			this.networkUse[this.activeIp] += size;
			this.networkCount[this.activeIp]++;
		} else {
			this.networkUse[this.activeIp] = size;
			this.networkCount[this.activeIp] = 1;
		}
	},

	writeNetworkUse() {
		let buf = '';
		for (let i in this.networkUse) {
			buf += `${this.networkUse[i]}\t${this.networkCount[i]}\t${i}\n`;
		}
		FS('logs/networkuse.tsv').write(buf);
	},

	clearNetworkUse() {
		if (Config.emergency) {
			this.networkUse = {};
			this.networkCount = {};
		}
	},

	/**
	 * Counts roughly the size of an object to have an idea of the server load.
	 *
	 * @param {any} object
	 * @return {number}
	 */
	sizeOfObject(object) {
		/** @type {Set<(Array | Object)>} */
		let objectCache = new Set();
		let stack = [object];
		let bytes = 0;

		while (stack.length) {
			let value = stack.pop();
			switch (typeof value) {
			case 'boolean':
				bytes += 4;
				break;
			case 'string':
				bytes += value.length * 2;
				break;
			case 'number':
				bytes += 8;
				break;
			case 'object':
				if (!objectCache.has(value)) objectCache.add(value);
				if (Array.isArray(value)) {
					for (let el of value) stack.push(el);
				} else {
					for (let i in value) stack.push(value[i]);
				}
				break;
			}
		}

		return bytes;
	},

	/** @type {{new(entries: [any, [number, number]]): TimedCounter}} */
	TimedCounter,
};

Monitor.cleanInterval = setInterval(() => Monitor.clean(), MONITOR_CLEAN_TIMEOUT);
<|endoftext|>


<commit_before>import React from 'react';

import { Well } from 'react-bootstrap';

export const Disclaimer = () => (

  <Well bsSize={'small'}>

      News

      Experimental: You can now copy/paste URLs of your favourite examples!
      (Countries and std/adv filters are stored).

      Most recently added countries: Iran (experimental, due to Farsi)
      - if you have feedback on the implementation, please contact me!

      Previously: Finland, Norway, Sweden, Hungary

Please{' '}

        me{' '}
      if
      you'd like to see new countries (which?) or features (what?).

Also, if you have
      feedback on the data quality per country,
      please message me! Thanks!

  </Well>
  Please note: Large amounts of data might be transferred!

Data/Licenses

  Data based on

      OpenStreetMap (under{' '}
      Open Data Commons Open Database License (ODbL)
      ). We have 3 levels of depth:

All Places

Inhabited Places

Filtered Inhabited Places

      Find the filter-queries{' '}

        here.

  Note that the original work by Moritz Stefaner is based on GeoNames.
  We chose OpenStreetMap here.

Inspiration & Thanks

Idea, visualization, examples are based on{' '}
    Moritz Stefaner's{' '}
    work, see{' '}
    here.

Big thanks and huge credit to his work
    (source here)!

Also, many thanks for the support and continuous feedback to{' '}
    OpenDataZurich!

Note

This is optimized for laptops/desktops,
    i.e. sufficiently large displays and a "pointer"
    device of sorts (fingers do not count here).

Still, it runs on phones and tablets, but buttons may be small...

Internet Explorer is not supported!

Source etc.

Find the source for this
    project here.
    Feedback (as always) much appreciated! (as are PRs, Forks, ...!)

Also, please contact me if you'd like new countries, or have better data, ...

Follow me{' '}
    for updates (performance, features, etc.)!

© 2016 bgrsquared consulting AG

);
<commit_msg>Update disclaimer.<commit_after>import React from 'react';

import { Well } from 'react-bootstrap';

export const Disclaimer = () => (

  <Well bsSize={'small'}>

      News

      Click to freeze a node (click it again (or specific button) to release it!)

      Most recently added countries: Iran (experimental, due to Farsi)
      - if you have feedback on the implementation, please contact me!

      Previously: Finland, Norway, Sweden, Hungary

Please{' '}

        me{' '}
      if
      you'd like to see new countries (which?) or features (what?).

Also, if you have
      feedback on the data quality per country,
      please message me! Thanks!

  </Well>
  Please note: Large amounts of data might be transferred!

Data/Licenses

  Data based on

      OpenStreetMap (under{' '}
      Open Data Commons Open Database License (ODbL)
      ). We have 3 levels of depth:

All Places

Inhabited Places

Filtered Inhabited Places

      Find the filter-queries{' '}

        here.

  Note that the original work by Moritz Stefaner is based on GeoNames.
  We chose OpenStreetMap here.

Inspiration & Thanks

Idea, visualization, examples are based on{' '}
    Moritz Stefaner's{' '}
    work, see{' '}
    here.

Big thanks and huge credit to his work
    (source here)!

Also, many thanks for the support and continuous feedback to{' '}
    OpenDataZurich!

Note

This is optimized for laptops/desktops,
    i.e. sufficiently large displays and a "pointer"
    device of sorts (fingers do not count here).

Still, it runs on phones and tablets, but buttons may be small...

Internet Explorer is not supported!

Source etc.

Find the source for this
    project here.
    Feedback (as always) much appreciated! (as are PRs, Forks, ...!)

Also, please contact me if you'd like new countries, or have better data, ...

Follow me{' '}
    for updates (performance, features, etc.)!

© 2016 bgrsquared consulting AG

);
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import {
  StyleSheet,
  View,
  Text,
  Button,
  TouchableOpacity,
  Easing,
  Animated,
  Image
} from 'react-native';
import * as Colors from './Colors';

export class BaconTimerScreen extends Component {
    static navigationOptions = {
        title: "Bakin' Bacon Timer",
        tabBarLabel: 'Timer',
    };
    constructor(props) {
      super(props);
      this.state = {
        duration: 1200,
        timer: null,
        running: false
      };
    }

    render() {
        const onPressControl = this.onPressControl.bind(this);
        return (
            <View style={styles.container}>
                <Text style={styles.timerText}>{this.timeLeft()}</Text>
                <Image
                    style={ styles.image }
                    source={require('./images/pig.png')}
                />
                <TouchableOpacity
                    style={ styles.controlContainer }
                    onPress={onPressControl}
                    >
                    {this.controlImage()}
                </TouchableOpacity>
            </View>
        );
    }

    setTimer() {
        if( this.state.running) {
            this._interval = setInterval(() => {
                if(this.state.duration > 0 ) {
                    this.setState({duration: this.state.duration - 1});
                }
                else
                {
                    // NOTIFY USER DONE.

                }
            }, 1000);
        } else {
            clearInterval(this._interval);
            this.setState({duration: 1200});
        }
    }

    async toggleRunning() {
        await this.setState({running: !this.state.running});
        this.setTimer();
    }

    onPressControl() {
        this.toggleRunning();
    }

    timeLeft() {
        var minutes = Math.floor(this.state.duration / 60);
        var seconds = this.state.duration % 60;
        return (minutes == 0 ? "0" : minutes) + ":" + (seconds == 0 ? "00" : (seconds < 10) ? "0" + seconds : seconds);
    }

    controlImage() {
        if (this.state.running) {
            return (
                    <Image
                        style={ styles.controlImage }
                        source={require('./images/reset.png')}
                    />
                )
        }
        return (
            <Image
                    style={ styles.controlImage }
                    source={require('./images/play.png')}
                />
            )
    }
}

// // First set up animation
// Animated.timing(
//     this.state.spinValue,
//   {
//     toValue: 1,
//     duration: 1200,
//     easing: Easing.linear
//   }
// ).start()
//
// // Second interpolate beginning and end values (in this case 0 and 1)
// const spin = this.state.spinValue.interpolate({
//   inputRange: [0, 1],
//   outputRange: ['0deg', '180deg']
// })

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: Colors.backgroundColor,
    alignItems: 'center'
  },
  timerText: {
      fontSize: 72,
      textAlign: 'center',
      margin: 10,
      marginBottom: 20,
      marginTop: 30,
  },
  button: {
      height: 60,
      justifyContent: 'center',
      alignItems: 'center',
  },
  imageContainer: {
    height:156,
    width: 156,
    borderRadius: 64,
  },
  image: {
    height:156,
    width: 156,
    borderRadius: 64
  },
  controlContainer: {
    height:96,
    width: 96,
    borderRadius: 32,
    marginTop: 40,
  },
  controlImage: {
    height:96,
    width: 96,
    borderRadius: 32
  },
});
<commit_msg>Added Vibration when timer is done.<commit_after>import React, { Component } from 'react';
import {
  StyleSheet,
  View,
  Text,
  Button,
  TouchableOpacity,
  Easing,
  Animated,
  Image,
  Vibration,
  Alert
} from 'react-native';
import * as Colors from './Colors';

export class BaconTimerScreen extends Component {
    static navigationOptions = {
        title: "Bakin' Bacon Timer",
        tabBarLabel: 'Timer',
    };
    constructor(props) {
      super(props);
      this.state = {
        duration: 10,
        timer: null,
        running: false
      };
    }

    render() {
        const onPressControl = this.onPressControl.bind(this);
        return (
            <View style={styles.container}>
                <Text style={styles.timerText}>{this.timeLeft()}</Text>
                <Image
                    style={ styles.image }
                    source={require('./images/pig.png')}
                />
                <TouchableOpacity
                    style={ styles.controlContainer }
                    onPress={onPressControl}
                    >
                    {this.controlImage()}
                </TouchableOpacity>
            </View>
        );
    }

    alertUser() {
        Vibration.vibrate([0, 200, 400, 600, 0, 100, 300, 500, 0, 200, 400, 500, 0, 100, 300, 500, 0, 200, 400, 600]);
        Alert.alert('Finished',
            'Go eat your perfect bacon.',
            [
                {text: 'OK', onPress: () => {
                    Vibration.cancel();
                }}
            ],
            { cancelable: false }
        )
    }

    setTimer() {
        if( this.state.running) {
            this._interval = setInterval(() => {
                if(this.state.duration > 0 ) {
                    this.setState({duration: this.state.duration - 1});
                }
                else
                {
                    clearInterval(this._interval);
                    this.alertUser();
                }
            }, 1000);
        } else {
            clearInterval(this._interval);
            this.setState({duration: 1200});
        }
    }

    async toggleRunning() {
        await this.setState({running: !this.state.running});
        this.setTimer();
    }

    onPressControl() {
        this.toggleRunning();
    }

    timeLeft() {
        var minutes = Math.floor(this.state.duration / 60);
        var seconds = this.state.duration % 60;
        return (minutes == 0 ? "0" : minutes) + ":" + (seconds == 0 ? "00" : (seconds < 10) ? "0" + seconds : seconds);
    }

    controlImage() {
        if (this.state.running) {
            return (
                    <Image
                        style={ styles.controlImage }
                        source={require('./images/reset.png')}
                    />
                )
        }
        return (
            <Image
                    style={ styles.controlImage }
                    source={require('./images/play.png')}
                />
            )
    }
}

// First set up animation
// Animated.timing(
//     this.state.spinValue,
//   {
//     toValue: 1,
//     duration: this.state.duration,
//     easing: Easing.linear
//   }
// ).start()
//
// // Second interpolate beginning and end values (in this case 0 and 1)
// const spin = this.state.spinValue.interpolate({
//   inputRange: [0, 1],
//   outputRange: ['0deg', '180deg']
// })

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: Colors.backgroundColor,
    alignItems: 'center'
  },
  timerText: {
      fontSize: 72,
      textAlign: 'center',
      margin: 10,
      marginBottom: 20,
      marginTop: 30,
  },
  button: {
      height: 60,
      justifyContent: 'center',
      alignItems: 'center',
  },
  imageContainer: {
    height:156,
    width: 156,
    borderRadius: 64,
  },
  image: {
    height:156,
    width: 156,
    borderRadius: 64
  },
  controlContainer: {
    height:96,
    width: 96,
    borderRadius: 32,
    marginTop: 40,
  },
  controlImage: {
    height:96,
    width: 96,
    borderRadius: 32
  },
});
<|endoftext|>


<commit_before>angular.module("yds").directive("ydsChord", ["$ocLazyLoad", "Data", "Filters", function ($ocLazyLoad, Data, Filters) {
    return {
        restrict: "E",
        scope: {
            projectId: "@",         // ID of the project
            viewType: "@",          // View type of data
            lang: "@",              // Language of the visualised data

            extraParams: "=",       // Extra attributes to pass to the API, if needed

            elementH: "@",          // Set the height of the component
            titleSize: "@",         // The size of the chart's main title

            addToBasket: "@",       // Enable or disable "add to basket" functionality, values: true, false
            embeddable: "@",        // Enable or disabled the embedding of the component
            popoverPos: "@",        // The side of the embed button from which the embed information window will appear

            enableRating: "@",      // Enable rating buttons for this component
            disableExplanation: "@" // Set to true to disable the explanation button
        },
        templateUrl: Data.templatePath + "templates/visualisation/chord.html",
        link: function (scope, element) {
            var chordContainer = _.first(angular.element(element[0].querySelector(".chord-container")));

            // Create a random id for the element that will render the chart
            scope.elementId = "bar" + Data.createRandomId();
            chordContainer.id = scope.elementId;

            var projectId = scope.projectId;
            var viewType = scope.viewType;
            var lang = scope.lang;
            var elementH = scope.elementH;
            var titleSize = scope.titleSize;
            var extraParams = scope.extraParams;

            // If extra params exist, add them to Filters
            if (!_.isUndefined(extraParams) && !_.isEmpty(extraParams)) {
                Filters.addExtraParamsFilter(scope.elementId, extraParams);
            }

            // Check if the projectId attribute is defined, else stop the process
            if (_.isUndefined(projectId) || projectId.trim() === "") {
                scope.ydsAlert = "The YDS component is not properly configured. " +
                    "Please check the corresponding documentation section.";
                return false;
            }

            // Check if view-type attribute is empty and assign the default value
            if (_.isUndefined(viewType) || viewType.trim() === "")
                viewType = "default";

            // Check if the language attribute is defined, else assign default value
            if (_.isUndefined(lang) || lang.trim() === "")
                lang = "en";

            // Check if the enablePaging attribute is defined, else assign default value
            if (_.isUndefined(scope.enablePaging) || (scope.enablePaging !== "true" && scope.enablePaging !== "false"))
                scope.enablePaging = "false";

            // Check if the component's height attribute is defined, else assign default value
            if (_.isUndefined(elementH) || _.isNaN(elementH))
                elementH = 200;

            // Check if the component's title size attribute is defined, else assign default value
            if (_.isUndefined(titleSize) || titleSize.length === 0 || _.isNaN(titleSize))
                titleSize = 18;

            var chart = null;

            // Show loading animation
            scope.loading = true;

            // Set the height of the chart
            chordContainer.style.height = elementH + "px";

            var createChord = function () {
                var params = _.clone(extraParams);

                if (_.isUndefined(params)) {
                    //todo: can remove if no extra things are added to it
                    params = {};
                }

                // Get data and visualize bar
                Data.getProjectVis("chord", projectId, viewType, lang, params)
                    .then(function (response) {
                        if (_.isNull(chart)) {
                            // Check that the component has not been destroyed
                            if (scope.$$destroyed)
                                return;

                            var options = response.data;

                            //////////////////////// CHORD ////////////////////////
                            var width = elementH,
                                height = elementH,
                                outerRadius = Math.min(width, height) / 2 - 10,
                                innerRadius = outerRadius - 24;

                            var formatPercent = d3.format(".1%");

                            var arc = d3.svg.arc()
                                .innerRadius(innerRadius)
                                .outerRadius(outerRadius);

                            var layout = d3.layout.chord()
                                .padding(.04)
                                .sortSubgroups(d3.descending)
                                .sortChords(d3.ascending);

                            var path = d3.svg.chord()
                                .radius(innerRadius);

                            var svg = d3.select(".chord-container").append("svg")
                                .attr("width", width)
                                .attr("height", height)
                                .append("g")
                                .attr("id", "circle")
                                .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

                            svg.append("circle")
                                .attr("r", outerRadius);

                            // Get data from API response
                            var matrix = response.data.matrix;
                            var items = response.data.nodes;

                            // Compute the chord layout.
                            layout.matrix(matrix);

                            // Add a group per neighborhood.
                            var group = svg.selectAll(".group")
                                .data(layout.groups)
                                .enter().append("g")
                                .attr("class", "group")
                            ;
                            // .on("mouseover", mouseover);

                            // Add a mouseover title.
                            group.append("title").text(function (d, i) {
                                return items[i].name + ": " + formatPercent(d.value) + " of origins";
                            });

                            // Add the group arc.
                            var groupPath = group.append("path")
                                .attr("id", function (d, i) {
                                    return "group" + i;
                                })
                                .attr("d", arc)
                                .style("fill", function (d, i) {
                                    return items[i].colour;
                                });

                            // Add a text label.
                            var groupText = group.append("text")
                                .attr("x", 6)
                                .attr("dy", 15);

                            groupText.append("textPath")
                                .attr("xlink:href", function (d, i) {
                                    return "#group" + i;
                                })
                                .text(function (d, i) {
                                    return items[i].name;
                                });

                            // Remove the labels that don't fit. :(
                            groupText.filter(function (d, i) {
                                return groupPath[0][i].getTotalLength() / 2 - 16 < this.getComputedTextLength();
                            })
                                .remove();

                            // Add the chords.
                            var chord = svg.selectAll(".chord")
                                .data(layout.chords)
                                .enter().append("path")
                                .attr("class", "chord")
                                .style("fill", function (d) {
                                    return items[d.source.index].colour;
                                })
                                .attr("d", path);

                            // Add an elaborate mouseover title for each chord.
                            // chord.append("title").text(function (d) {
                            //     return cities[d.source.index].name
                            //         + " → " + cities[d.target.index].name
                            //         + ": " + formatPercent(d.source.value)
                            //         + "\n" + cities[d.target.index].name
                            //         + " → " + cities[d.source.index].name
                            //         + ": " + formatPercent(d.target.value);
                            // });
                            //
                            // function mouseover(d, i) {
                            //     chord.classed("fade", function (p) {
                            //         return p.source.index != i
                            //             && p.target.index != i;
                            //     });
                            // }

                            //////////////////////// CHORD ////////////////////////

                            console.log("Chord", options);
                            scope.chordData = options;
                        } else {
                            // Update the chart's options
                            console.log("Chord update");
                        }

                        // Remove loading animation
                        scope.loading = false;
                    }, function (error) {
                        if (_.isNull(error) || _.isUndefined(error) || _.isUndefined(error.message))
                            scope.ydsAlert = "An error has occurred, please check the configuration of the component";
                        else
                            scope.ydsAlert = error.message;

                        // Remove loading animation
                        scope.loading = false;
                    });
            };

            if (!_.has(window, "d3")) {
                $ocLazyLoad.load({
                    files: ["https://d3js.org/d3.v3.min.js"],
                    cache: true
                }).then(function () {
                    createChord();
                });
            } else {
                // Create the bar
                createChord();
            }
        }
    };
}]);
<commit_msg>Add mouseover to chord<commit_after>angular.module("yds").directive("ydsChord", ["$ocLazyLoad", "Data", "Filters", function ($ocLazyLoad, Data, Filters) {
    return {
        restrict: "E",
        scope: {
            projectId: "@",         // ID of the project
            viewType: "@",          // View type of data
            lang: "@",              // Language of the visualised data

            extraParams: "=",       // Extra attributes to pass to the API, if needed

            elementH: "@",          // Set the height of the component
            titleSize: "@",         // The size of the chart's main title

            addToBasket: "@",       // Enable or disable "add to basket" functionality, values: true, false
            embeddable: "@",        // Enable or disabled the embedding of the component
            popoverPos: "@",        // The side of the embed button from which the embed information window will appear

            enableRating: "@",      // Enable rating buttons for this component
            disableExplanation: "@" // Set to true to disable the explanation button
        },
        templateUrl: Data.templatePath + "templates/visualisation/chord.html",
        link: function (scope, element) {
            var chordContainer = _.first(angular.element(element[0].querySelector(".chord-container")));

            // Create a random id for the element that will render the chart
            scope.elementId = "bar" + Data.createRandomId();
            chordContainer.id = scope.elementId;

            var projectId = scope.projectId;
            var viewType = scope.viewType;
            var lang = scope.lang;
            var elementH = scope.elementH;
            var titleSize = scope.titleSize;
            var extraParams = scope.extraParams;

            // If extra params exist, add them to Filters
            if (!_.isUndefined(extraParams) && !_.isEmpty(extraParams)) {
                Filters.addExtraParamsFilter(scope.elementId, extraParams);
            }

            // Check if the projectId attribute is defined, else stop the process
            if (_.isUndefined(projectId) || projectId.trim() === "") {
                scope.ydsAlert = "The YDS component is not properly configured. " +
                    "Please check the corresponding documentation section.";
                return false;
            }

            // Check if view-type attribute is empty and assign the default value
            if (_.isUndefined(viewType) || viewType.trim() === "")
                viewType = "default";

            // Check if the language attribute is defined, else assign default value
            if (_.isUndefined(lang) || lang.trim() === "")
                lang = "en";

            // Check if the enablePaging attribute is defined, else assign default value
            if (_.isUndefined(scope.enablePaging) || (scope.enablePaging !== "true" && scope.enablePaging !== "false"))
                scope.enablePaging = "false";

            // Check if the component's height attribute is defined, else assign default value
            if (_.isUndefined(elementH) || _.isNaN(elementH))
                elementH = 200;

            // Check if the component's title size attribute is defined, else assign default value
            if (_.isUndefined(titleSize) || titleSize.length === 0 || _.isNaN(titleSize))
                titleSize = 18;

            var chart = null;

            // Show loading animation
            scope.loading = true;

            // Set the height of the chart
            chordContainer.style.height = elementH + "px";

            /**
             * Chord mouseover function
             */
            var mouseover = function (d, i) {
                chord.classed("fade", function (p) {
                    return p.source.index != i
                        && p.target.index != i;
                });
            };

            var createChord = function () {
                var params = _.clone(extraParams);

                if (_.isUndefined(params)) {
                    //todo: can remove if no extra things are added to it
                    params = {};
                }

                // Get data and visualize bar
                Data.getProjectVis("chord", projectId, viewType, lang, params)
                    .then(function (response) {
                        if (_.isNull(chart)) {
                            // Check that the component has not been destroyed
                            if (scope.$$destroyed)
                                return;

                            var options = response.data;

                            //////////////////////// CHORD ////////////////////////
                            var width = elementH,
                                height = elementH,
                                outerRadius = Math.min(width, height) / 2 - 10,
                                innerRadius = outerRadius - 24;

                            var formatPercent = d3.format(".1%");

                            var arc = d3.svg.arc()
                                .innerRadius(innerRadius)
                                .outerRadius(outerRadius);

                            var layout = d3.layout.chord()
                                .padding(.04)
                                .sortSubgroups(d3.descending)
                                .sortChords(d3.ascending);

                            var path = d3.svg.chord()
                                .radius(innerRadius);

                            var svg = d3.select(".chord-container").append("svg")
                                .attr("width", width)
                                .attr("height", height)
                                .append("g")
                                .attr("id", "circle")
                                .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

                            svg.append("circle")
                                .attr("r", outerRadius);

                            // Get data from API response
                            var matrix = response.data.matrix;
                            var items = response.data.nodes;

                            // Compute the chord layout.
                            layout.matrix(matrix);

                            // Add a group per neighborhood.
                            var group = svg.selectAll(".group")
                                .data(layout.groups)
                                .enter().append("g")
                                .attr("class", "group")
                                .on("mouseover", mouseover);

                            // Add a mouseover title.
                            group.append("title").text(function (d, i) {
                                return items[i].name + ": " + formatPercent(d.value) + " of origins";
                            });

                            // Add the group arc.
                            var groupPath = group.append("path")
                                .attr("id", function (d, i) {
                                    return "group" + i;
                                })
                                .attr("d", arc)
                                .style("fill", function (d, i) {
                                    return items[i].colour;
                                });

                            // Add a text label.
                            var groupText = group.append("text")
                                .attr("x", 6)
                                .attr("dy", 15);

                            groupText.append("textPath")
                                .attr("xlink:href", function (d, i) {
                                    return "#group" + i;
                                })
                                .text(function (d, i) {
                                    return items[i].name;
                                });

                            // Remove the labels that don't fit. :(
                            groupText.filter(function (d, i) {
                                return groupPath[0][i].getTotalLength() / 2 - 16 < this.getComputedTextLength();
                            })
                                .remove();

                            // Add the chords.
                            var chord = svg.selectAll(".chord")
                                .data(layout.chords)
                                .enter().append("path")
                                .attr("class", "chord")
                                .style("fill", function (d) {
                                    return items[d.source.index].colour;
                                })
                                .attr("d", path);

                            // Add an elaborate mouseover title for each chord.
                            chord.append("title").text(function (d) {
                                return items[d.source.index].name
                                    + " → " + items[d.target.index].name
                                    + ": " + formatPercent(d.source.value)
                                    + "\n" + items[d.target.index].name
                                    + " → " + items[d.source.index].name
                                    + ": " + formatPercent(d.target.value);
                            });
                            //////////////////////// CHORD ////////////////////////

                            console.log("Chord", options);
                            scope.chordData = options;
                        } else {
                            // Update the chart's options
                            console.log("Chord update");
                        }

                        // Remove loading animation
                        scope.loading = false;
                    }, function (error) {
                        if (_.isNull(error) || _.isUndefined(error) || _.isUndefined(error.message))
                            scope.ydsAlert = "An error has occurred, please check the configuration of the component";
                        else
                            scope.ydsAlert = error.message;

                        // Remove loading animation
                        scope.loading = false;
                    });
            };

            if (!_.has(window, "d3")) {
                $ocLazyLoad.load({
                    files: ["https://d3js.org/d3.v3.min.js"],
                    cache: true
                }).then(function () {
                    createChord();
                });
            } else {
                // Create the bar
                createChord();
            }
        }
    };
}]);
<|endoftext|>


<commit_before>describe("Reformed Churches Locator", function() {

    afterEach(function() {
        // Returns the page to display the home page as it should
        app.navigate(config.test_home_address, {trigger:true})
    });

    describe("'Find a church' page", function() {
        it("should not display an error", function() {
            app.navigate('find_a_church', { trigger: true })
            var content = $('#content').html()
            expect(content).toMatch('Find a church')
        });
    });

    describe("'Import a directory' page", function() {
        it("should not display an error", function() {
            app.navigate('import_directory', { trigger: true })
            var content = $('#content').html()
            expect(content).toMatch('downloading')
        });
        function trigger_url_field(){
            $('#url').val('http://opc.org/locator.html')
            $('#url').focus().keyup()
        }
        function directory_type_is_visible(){
            // The test should wait until the AJAX call completes
            // Check to see if the AJAX call returned here
            return $('#directory_type').is(':visible')
        }
        function click_state_radio_button(){
            $("#one_state_per_page").prop("checked",true)
            $("#one_state_per_page").click()
        }
        function state_page_is_visible(){
            return $('#state_page').is(':visible') || ($('#state_page').css('display') != 'none')
        }
        describe('url field', function(){
            it('should display step 2 when a valid URL is entered', function(){
                // TODO: Because this test writes to the database, decide whether
                //  the tests should run in a test copy of the database.
                runs(trigger_url_field)
                waitsFor(directory_type_is_visible, "AJAX call to get URL HTML", 40000)
                runs(function(){
                    expect($('#directory_type').is(':visible')).toBe(true)
                    // Remove the docs we just created
                    // TODO: Figure out how to refactor this into a general function that can be used in all tests
//                    dir.destroy()
                    // TODO: Improve this to only remove the docs created by this test, rather
                    //  than all docs.
//                    $('#delete_all_docs').click()
                })
            })
        })

        describe('state section', function(){
            it('should display when radio button is clicked', function(){
                runs(trigger_url_field)
                waitsFor(directory_type_is_visible, "AJAX call to get URL HTML")
                runs(click_state_radio_button)
                waitsFor(state_page_is_visible, 'state page to display', 20000)
                runs(function(){
                    expect($('#state_page').is(':visible')).toBe(true)
                    // Remove the docs we just created
//                    dir.destroy()
//                    $('#delete_all_docs').click()
                })
            })
        })
        describe('cong details', function(){
            it('should display when select box is clicked', function(){
                runs(trigger_url_field)
                waitsFor(directory_type_is_visible, "AJAX call to get URL HTML")
                runs(click_state_radio_button)
                waitsFor(state_page_is_visible, 'state page to display', 20000)
                runs(function(){
                    $('#state_drop_down_selector select[name=state]').click()
                })
                waitsFor(function(){
                    return $('#cong_details_url_selector').is(':visible')
                },'cong_details_url_selector to be visible', 20000)
                runs(function(){
                    expect($('#cong_details_url_selector').is(':visible')).toBe(true)
                    // Remove the docs we just created
//                    dir.destroy()
//                    $('#delete_all_docs').click()
                })
            })
        })
        // TODO: Write test to click on the element with this XPATH:
        //  //*[@id="churchListTable"]/tbody/tr[2]/td[1]/a
    });
});

// General test-runner code
(function() {
    var jasmineEnv = jasmine.getEnv();
    jasmineEnv.updateInterval = 1000;
    var htmlReporter = new jasmine.HtmlReporter();
    jasmineEnv.addReporter(htmlReporter);
    jasmineEnv.specFilter = function(spec) {
        return htmlReporter.specFilter(spec);
    };
    var currentWindowOnload = window.onload;
    window.onload = function() {
        if (currentWindowOnload) {
            currentWindowOnload();
        }
        execJasmine();
    };
    function execJasmine() {
        jasmineEnv.execute();
        // Move Jasmine's symbolSummary to top of page to make it easy to see if
        //    some tests are not passing
        $('body').prepend('

')
        $('.banner').first().prepend($('.symbolSummary'))
    }
})();<commit_msg>Getting unit test to work.<commit_after>describe("Reformed Churches Locator", function() {

    afterEach(function() {
        // Returns the page to display the home page as it should
        app.navigate(config.test_home_address, {trigger:true})
    });

    describe("'Find a church' page", function() {
        it("should not display an error", function() {
            app.navigate('find_a_church', { trigger: true })
            var content = $('#content').html()
            expect(content).toMatch('Find a church')
        });
    });

    describe("'Import a directory' page", function() {
        it("should not display an error", function() {
            app.navigate('import_directory', { trigger: true })
            var content = $('#content').html()
            expect(content).toMatch('downloading')
        });
        function trigger_url_field(){
            $('#url').val('http://opc.org/locator.html')
            $('#url').focus().keyup()
        }
        function directory_type_is_visible(){
            // The test should wait until the AJAX call completes
            // Check to see if the AJAX call returned here
            return $('#directory_type').is(':visible')
        }
        function click_state_radio_button(){
            $("#one_state_per_page").prop("checked",true)
            $("#one_state_per_page").click()
        }
        function state_page_is_visible(){
            return $('#state_page').is(':visible') || ($('#state_page').css('display') != 'none')
        }
        describe('url field', function(){
            it('should display step 2 when a valid URL is entered', function(){
                // TODO: Because this test writes to the database, decide whether
                //  the tests should run in a test copy of the database.
                runs(trigger_url_field)
                waitsFor(directory_type_is_visible, "AJAX call to get URL HTML", 60000)
                runs(function(){
                    expect($('#directory_type').is(':visible')).toBe(true)
                    // Remove the docs we just created
                    // TODO: Figure out how to refactor this into a general function that can be used in all tests
//                    dir.destroy()
                    // TODO: Improve this to only remove the docs created by this test, rather
                    //  than all docs.
//                    $('#delete_all_docs').click()
                })
            })
        })

        describe('state section', function(){
            it('should display when radio button is clicked', function(){
                runs(trigger_url_field)
                waitsFor(directory_type_is_visible, "AJAX call to get URL HTML")
                runs(click_state_radio_button)
                waitsFor(state_page_is_visible, 'state page to display', 20000)
                runs(function(){
                    expect($('#state_page').is(':visible')).toBe(true)
                    // Remove the docs we just created
//                    dir.destroy()
//                    $('#delete_all_docs').click()
                })
            })
        })
        describe('cong details', function(){
            it('should display when select box is clicked', function(){
                runs(trigger_url_field)
                waitsFor(directory_type_is_visible, "AJAX call to get URL HTML")
                runs(click_state_radio_button)
                waitsFor(state_page_is_visible, 'state page to display', 20000)
                runs(function(){
                    $('#state_drop_down_selector select[name=state]').click()
                })
                waitsFor(function(){
                    return $('#cong_details_url_selector').is(':visible')
                },'cong_details_url_selector to be visible', 20000)
                runs(function(){
                    expect($('#cong_details_url_selector').is(':visible')).toBe(true)
                    // Remove the docs we just created
//                    dir.destroy()
//                    $('#delete_all_docs').click()
                })
            })
        })
        // TODO: Write test to click on the element with this XPATH:
        //  //*[@id="churchListTable"]/tbody/tr[2]/td[1]/a
        describe('get congregation id', function(){
            it('should display when link is clicked', function(){
                runs(trigger_url_field)
                waitsFor(directory_type_is_visible, "AJAX call to get URL HTML")
                runs(click_state_radio_button)
                waitsFor(state_page_is_visible, 'state page to display', 20000)
                runs(function(){
                    $('#state_drop_down_selector select[name=state]').click()
                })
                waitsFor(function(){
                    return $('#cong_details_url_selector').is(':visible')
                },'cong_details_url_selector to be visible', 60000)
                runs(function(){
                    expect($('#cong_details_url_selector').is(':visible')).toBe(true)
                    // Remove the docs we just created
//                    dir.destroy()
//                    $('#delete_all_docs').click()
                })
                waitsFor(function(){
                    return $('#cong_details_url_selector').html().indexOf('Step 4.5') != -1
                },'cong_details_url_selector to display step 4.5', 60000)
                runs(function(){
                    expect($('#cong_details_url_selector').html()).toMatch('Step 4.5')
                    // Remove the docs we just created
//                    dir.destroy()
//                    $('#delete_all_docs').click()
                })
            })
        })
    });
});

// General test-runner code
(function() {
    var jasmineEnv = jasmine.getEnv();
    jasmineEnv.updateInterval = 1000;
    var htmlReporter = new jasmine.HtmlReporter();
    jasmineEnv.addReporter(htmlReporter);
    jasmineEnv.specFilter = function(spec) {
        return htmlReporter.specFilter(spec);
    };
    var currentWindowOnload = window.onload;
    window.onload = function() {
        if (currentWindowOnload) {
            currentWindowOnload();
        }
        execJasmine();
    };
    function execJasmine() {
        jasmineEnv.execute();
        // Move Jasmine's symbolSummary to top of page to make it easy to see if
        //    some tests are not passing
        $('body').prepend('

')
        $('.banner').first().prepend($('.symbolSummary'))
    }
})();<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var yaml = require('yaml-front-matter');
var _ = require('lodash');

/*
 * export
 */
module.exports = function(collectionRoot, playlistFolder){
  // return playlists found in collectionRoot/playlistFolder
  var playlistRoot = path.join(collectionRoot, playlistFolder);

  if (!fs.existsSync(playlistRoot)) {
    return [];
  }

  // create a list of playlists
  var playlistFiles = fs.readdirSync(playlistRoot);
  playlistFiles = _.map(playlistFiles, withPath, {root: playlistRoot});
  playlistFiles = _.filter(playlistFiles, isPlaylist);

  var playlists = _.map(playlistFiles, getPlaylist, {root: collectionRoot});

  return playlists;
};

/*
 * helper functions
 */
function withPath(filename) {
  // add path to filename
  return path.join(this.root, filename);
}

function isPlaylist(filename) {
  // if not directory and ends with .txt
  return filename.match(/\.txt$/) &&
         !fs.statSync(filename).isDirectory();
}

function playlistId(name){
  // replace chars in playlist-name, so that it can be used as id or class
  var id = name.replace(/ /g, '_');
  id = id.replace(/[\,\.\-\?]/g, '');
  return id;
}

function getLink(root, filename) {
  var link = filename.replace('.md', '.html');
  link = path.relative(root, link);
  link = path.join(path.basename(root), link);
  return link;
}

function getPlaylist(filename) {
  var lessonFiles = fs.readFileSync(filename, {encoding: 'utf8'}).split('\n');
  lessonFiles = _.compact(lessonFiles); // omit empty lines
  lessonFiles = _.map(lessonFiles, withPath, {root: this.root});

  var playlist = {};
  playlist.name = path.basename(filename).replace('.txt', '').replace(/_/g, ' ');
  playlist.id = playlistId(playlist.name);
  playlist.lessons = _.map(lessonFiles, getFrontMatter, {root: this.root});

  return playlist;
}

function getFrontMatter(filename){
  // return front matter of file, its filename and link
  var content = fs.readFileSync(filename);

  var frontMatter = yaml.loadFront(content);
  frontMatter = _.omit(frontMatter, '__content');
  frontMatter.filename = filename;
  frontMatter.link = getLink(this.root, filename);

  return frontMatter;
}
<commit_msg>closes #71<commit_after>var fs = require('fs');
var path = require('path');
var yaml = require('yaml-front-matter');
var _ = require('lodash');

/*
 * export
 */
module.exports = function(collectionRoot, playlistFolder){
  // return playlists found in collectionRoot/playlistFolder
  var playlistRoot = path.join(collectionRoot, playlistFolder);

  if (!fs.existsSync(playlistRoot)) {
    return [];
  }

  // create a list of playlists
  var playlistFiles = fs.readdirSync(playlistRoot);
  playlistFiles = _.map(playlistFiles, withPath, {root: playlistRoot});
  playlistFiles = _.filter(playlistFiles, isPlaylist);

  var playlists = _.map(playlistFiles, getPlaylist, {root: collectionRoot});

  return playlists;
};

/*
 * helper functions
 */
function withPath(filename) {
  // add path to filename
  return path.join(this.root, filename);
}

function isPlaylist(filename) {
  // if not directory and ends with .txt
  return filename.match(/\.txt$/) &&
         !fs.statSync(filename).isDirectory();
}

function playlistId(name){
  // replace chars in playlist-name, so that it can be used as id or class
  var id = name.replace(/ /g, '_');
  id = id.replace(/[\,\.\-\?]/g, '');
  return id;
}

function getLink(root, filename) {
  var link = filename.replace('.md', '.html');
  link = path.relative(root, link);
  link = path.join(path.basename(root), link);
  return link;
}

function getPlaylist(filename) {
  var lessonFiles = fs.readFileSync(filename, {encoding: 'utf8'})
                      .replace(/\r/g, '')
                      .split('\n');
  lessonFiles = _.compact(lessonFiles); // omit empty lines
  lessonFiles = _.map(lessonFiles, withPath, {root: this.root});

  var playlist = {};
  playlist.name = path.basename(filename).replace('.txt', '').replace(/_/g, ' ');
  playlist.id = playlistId(playlist.name);
  playlist.lessons = _.map(lessonFiles, getFrontMatter, {root: this.root});

  return playlist;
}

function getFrontMatter(filename){
  // return front matter of file, its filename and link
  var content = fs.readFileSync(filename);

  var frontMatter = yaml.loadFront(content);
  frontMatter = _.omit(frontMatter, '__content');
  frontMatter.filename = filename;
  frontMatter.link = getLink(this.root, filename);

  return frontMatter;
}
<|endoftext|>


<commit_before>'use strict';

/*global nodeca, _*/

// 3rd-party
var Store = require('nlib').Settings.Store;
var async = require('nlib').Vendor.Async;

////////////////////////////////////////////////////////////////////////////////

// Helper to fetch usergroups by IDs
//
function fetchUserGroups(ids, callback) {
  nodeca.models.users.UserGroup.find().where('_id').in(ids).exec(callback);
}

// Memoized version of fetchUserGroups helper
//
var fetchUserGroupsCached = nodeca.components.memoizee(fetchUserGroups, {
  // momoizee options. revalidate cache after 30 sec
  async:  true,
  maxAge: 30000
});

////////////////////////////////////////////////////////////////////////////////

var UserGroupStore = new Store({
  get: function (key, params, options, callback) {
    var func = options.skipCache ? fetchUserGroups : fetchUserGroupsCached;

    func(params.usergroup_ids, function (err, grps) {
      if (err) {
        callback(err);
        return;
      }

      var values = grps.map(function (grp) {
        var value = grp.settings && grp.settings[key];

        // map to the list of settings key value
        return undefined === value ? null : value;
      }).filter(function (value) {
        // leave only those who have ones
        return !!value;
      });

      // push default value
      values.push({ value: UserGroupStore.getDefaultValue(key) });

      callback(null, Store.mergeValues(values));
    });
  },
  set: function (values, params, callback) {
    fetchUserGroups(params.usergroup_ids, function (err, groups) {
      if (err) {
        callback(err);
        return;
      }

      // leave only those params, that we know about
      values = _.pick(values || {}, UserGroupStore.keys);

      // set values for each usergroup
      async.forEach(groups, function (group, next) {
        if (!group.settings) {
          group.settings = {};
        }

        _.each(values, function (opts, key) {
          group.settings[key] = { value: opts.value, force: !!opts.value };
        });

        group.markModified('settings');
        group.save(next);
      }, callback);
    });
  },
  params: {
    usergroup_ids: {
      type: 'array',
      required: true,
      minItems: 1
    }
  }
});

////////////////////////////////////////////////////////////////////////////////

module.exports = UserGroupStore;
<commit_msg>Improve userstore<commit_after>'use strict';

/*global nodeca, _*/

// 3rd-party
var Store = require('nlib').Settings.Store;
var async = require('nlib').Vendor.Async;

////////////////////////////////////////////////////////////////////////////////

// Helper to fetch usergroups by IDs
//
function fetchUsrGrpSettings(ids, callback) {
  nodeca.models.users.UserGroup.find()
    .select('settings')
    .where('_id').in(ids)
    .exec(callback);
}

// Memoized version of fetchUserGroups helper
//
var fetchUsrGrpSettingsCached = nodeca.components.memoizee(fetchUsrGrpSettings, {
  // momoizee options. revalidate cache after 30 sec
  async:  true,
  maxAge: 30000
});

////////////////////////////////////////////////////////////////////////////////

var UserGroupStore = new Store({
  get: function (key, params, options, callback) {
    var func = options.skipCache ? fetchUsrGrpSettings : fetchUsrGrpSettingsCached;

    func(params.usergroup_ids, function (err, grps) {
      if (err) {
        callback(err);
        return;
      }

      var values = [];

      grps.forEach(function (grp) {
        if (grp.settings && grp.settings[key]) {
          values.push(grp.settings[key]);
        }
      });

      // push default value
      values.push({ value: UserGroupStore.getDefaultValue(key) });

      callback(null, Store.mergeValues(values));
    });
  },
  set: function (values, params, callback) {
    fetchUsrGrpSettings(params.usergroup_ids, function (err, grps) {
      if (err) {
        callback(err);
        return;
      }

      // leave only those params, that we know about
      values = _.pick(values || {}, UserGroupStore.keys);

      // set values for each usergroup
      async.forEach(grps, function (grp, next) {
        grp.settings = grp.settings || {};

        _.each(values, function (opts, key) {
          grp.settings[key] = {
            value: opts.value,
            force: !!opts.value
          };
        });

        grp.markModified('settings');
        grp.save(next);
      }, callback);
    });
  },
  params: {
    usergroup_ids: {
      type: 'array',
      required: true,
      minItems: 1
    }
  }
});

////////////////////////////////////////////////////////////////////////////////

module.exports = UserGroupStore;
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import './Details.css'

class Details extends Component {
  static propTypes = {
    title: PropTypes.string,
    image: PropTypes.string,
    tags: PropTypes.array,
    description: PropTypes.string,
  }

  render() {
    let { title, image, tags, description } = this.props

    if (title === undefined) {
      title = 'Titel fehlt noch'
    }
    if (image === undefined) {
      image = 'bude.jpg'
    }
    if (tags === undefined) {
      tags = []
    }
    if (description === undefined) {
      description = 'Beschreibung fehlt noch'
    }

    return (

There will be a header

{title}

            {tags.map(tag => (

                {tag}

            ))}

{description}

There might be a searchbar

    )
  }
}

export default Details
<commit_msg>Rename title to name<commit_after>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import './Details.css'

class Details extends Component {
  static propTypes = {
    name: PropTypes.string,
    image: PropTypes.string,
    tags: PropTypes.array,
    description: PropTypes.string,
  }

  render() {
    let { name, image, tags, description } = this.props

    if (name === undefined) {
      name = 'Titel fehlt noch'
    }
    if (image === undefined) {
      image = 'bude.jpg'
    }
    if (tags === undefined) {
      tags = []
    }
    if (description === undefined) {
      description = 'Beschreibung fehlt noch'
    }

    return (

There will be a header

{name}

            {tags.map(tag => (

                {tag}

            ))}

{description}

There might be a searchbar

    )
  }
}

export default Details
<|endoftext|>


<commit_before>#!/usr/bin/env node

var clivas = require('clivas')
var cp = require('child_process')
var fs = require('fs')
var minimist = require('minimist')
var moment = require('moment')
var networkAddress = require('network-address')
var path = require('path')
var prettysize = require('prettysize')
var WebTorrent = require('../')

process.title = 'WebTorrent'

process.on('exit', function (code) {
  if (code !== 0) {
    clivas.line('{red:ERROR:} If you think this is a bug in webtorrent, report it!')
    console.log('=====>                                               <=====')
    console.log('=====>  https://github.com/feross/webtorrent/issues  <=====')
    console.log('=====>                                               <=====')
  }
})

var argv = minimist(process.argv.slice(2), {
  alias: {
    p: 'port',
    b: 'blocklist',
    t: 'subtitles',
    l: 'list',
    i: 'index',
    o: 'out',
    q: 'quiet',
    h: 'help',
    v: 'version'
  },
  boolean: [ // options that are always boolean
    'airplay',
    'chromecast',
    'mplayer',
    'mpv',
    'vlc',
    'xbmc',
    'stdout',
    'list',
    'quiet',
    'help',
    'version'
  ],
  default: {
    port: 8000
  }
})

if (argv.version) {
  console.log(require('../package.json').version)
  return done()
}

var torrentId = argv._[0]

if (argv.help || !torrentId) {
  fs.readFileSync(path.join(__dirname, 'ascii-logo.txt'), 'utf8')
    .split('\n')
    .forEach(function (line) {
      clivas.line('{bold:' + line.substring(0, 20) + '}{red:' + line.substring(20) + '}')
    })

  console.log(function () {/*
  Usage:
      webtorrent <options> <torrent-uri>

      Download the torrent from:
          * magnet uri
          * http url to .torrent file
          * filesystem path to .torrent file
          * info hash (hex string)

  Streaming options:
      --airplay               Apple TV
      --chromecast            Chromecast
      --mplayer               MPlayer
      --mpv                   MPV
      --omx [jack]            omx [default: hdmi]
      --vlc                   VLC
      --xbmc                  XBMC
      --stdout                standard out (implies --quiet)

  Options:
      -o, --out [path]        set download destination [default: /tmp/webtorrent]
      -l, --list              list files in torrent (with indexes)
      -i, --index [index]     stream a particular file from torrent (by index)
      -p, --port [number]     change the http port [default: 8000]
      -b, --blocklist [path]  load blocklist file/http url
      -t, --subtitles [file]  load subtitles file
      -q, --quiet             don't show UI on stdout
      -v, --version           print the current version

  Please report bugs!  https://github.com/feross/webtorrent/issues

    */}.toString().split(/\n/).slice(1, -1).join('\n'))
  return done()
}

if (process.env.DEBUG || argv.stdout) {
  argv.quiet = argv.q = true
}

var VLC_ARGS = process.env.DEBUG
  ? '-q --play-and-exit'
  : '--play-and-exit --extraintf=http:logger --verbose=2 --file-logging --logfile=vlc-log.txt'
var MPLAYER_EXEC = 'mplayer -ontop -really-quiet -noidx -loop 0'
var MPV_EXEC = 'mpv --ontop --really-quiet --loop=no'
var OMX_EXEC = 'omxplayer -r -o ' + (typeof argv.omx === 'string')
  ? argv.omx
  : 'hdmi'

if (argv.subtitles) {
  VLC_ARGS += ' --sub-file=' + argv.subtitles
  MPLAYER_EXEC += ' -sub ' + argv.subtitles
  MPV_EXEC += ' --sub-file=' + argv.subtitles
  OMX_EXEC += ' --subtitles ' + argv.subtitles
}

function error (err) {
  clivas.line('{red:ERROR:} ' + (err.message || err))
}

function errorAndExit (err) {
  error(err)
  process.exit(1)
}

var started = Date.now()
function getRuntime () {
  return Math.floor((Date.now() - started) / 1000)
}

var client = new WebTorrent({
  blocklist: argv.blocklist
})
.on('error', errorAndExit)

if (!argv.out) { // If no output file has been specified
  process.on('SIGINT', remove)
  process.on('SIGTERM', remove)
}

function remove (cb) {
  process.removeListener('SIGINT', remove)
  process.removeListener('SIGTERM', remove)
  client.destroy(cb)
}

var torrent = client.add(torrentId, (argv.out ? { tmp: argv.out } : {}))

torrent.on('infoHash', function () {
  function updateMetadata () {
    var numPeers = torrent.swarm.numPeers
    clivas.clear()
    clivas.line('{green:fetching torrent metadata from} {bold:'+numPeers+'} {green:peers}')
  }

  if (!argv.quiet && !argv.list) {
    torrent.swarm.on('wire', updateMetadata)
    torrent.on('metadata', function () {
      torrent.swarm.removeListener('wire', updateMetadata)
    })
    updateMetadata()
  }
})

var filename, swarm, wires, server, serving

if (argv.list) torrent.once('ready', onReady)
else {
  server = torrent.createServer()
  server.listen(argv.port, function () {
    if (torrent.ready) onReady()
    else torrent.once('ready', onReady)
  }).once('connection', function () {
    serving = true
  })
}

function done () {
  if (!serving) {
    process.exit(0)
  }
}

function onReady () {
  filename = torrent.name
  swarm = torrent.swarm
  wires = torrent.swarm.wires

  if (argv.list) {
    torrent.files.forEach(function (file, i) {
      clivas.line('{3+bold:' + i + '} : {magenta:' + file.name + '}')
    })
    return done()
  }

  torrent.on('verifying', function (data) {
    if (argv.quiet) return
    clivas.clear()
    clivas.line(
      '{green:verifying existing torrent} {bold:'+Math.floor(data.percentDone)+'%} ' +
      '({bold:'+Math.floor(data.percentVerified)+'%} {green:passed verification})'
    )
  })

  torrent.on('done', function () {
    if (!argv.quiet) {
      // TODO: expose this data from bittorrent-swarm
      var numActiveWires = torrent.swarm.wires.reduce(function (num, wire) {
        return num + (wire.downloaded > 0)
      }, 0)
      clivas.line('torrent downloaded {green:successfully} from {bold:'+numActiveWires+'/'+torrent.swarm.wires.length+'} {green:peers} in {bold:'+getRuntime()+'s}!')
    }
    done()
  })

  var cmd, player
  var playerName = argv.airplay ? 'Airplay'
    : argv.chromecast ? 'Chromecast'
    : argv.xbmc ? 'XBMC'
    : argv.vlc ? 'VLC'
    : argv.mplayer ? 'MPlayer'
    : argv.mpv ? 'mpv'
    : argv.omx ? 'OMXPlayer'
    : null

  // if no index specified, use largest file
  var index = (typeof argv.index === 'number')
    ? argv.index
    : torrent.files.indexOf(torrent.files.reduce(function (a, b) {
      return a.length > b.length ? a : b
    }))
  var href = 'http://' + networkAddress() + ':' + argv.port + '/' + index

  if (playerName) torrent.files[index].select()
  if (argv.stdout) torrent.files[index].createReadStream().pipe(process.stdout)

  if (argv.vlc && process.platform === 'win32') {
    var registry = require('windows-no-runnable').registry
    var key
    if (process.arch === 'x64') {
      try {
        key = registry('HKLM/Software/Wow6432Node/VideoLAN/VLC')
      } catch (e) {}
    } else {
      try {
        key = registry('HKLM/Software/VideoLAN/VLC')
      } catch (err) {}
    }

    if (key) {
      var vlcPath = key.InstallDir.value + path.sep + 'vlc'
      VLC_ARGS = VLC_ARGS.split(' ')
      VLC_ARGS.unshift(href)
      cp.execFile(vlcPath, VLC_ARGS, function (err) {
        if (err) return errorAndExit(err)
        done()
      })
    }
  } else if (argv.vlc) {
    var root = '/Applications/VLC.app/Contents/MacOS/VLC'
    var home = (process.env.HOME || '') + root
    cmd = 'vlc ' + href + ' ' + VLC_ARGS + ' || ' +
      root + ' ' + href + ' ' + VLC_ARGS + ' || ' +
      home + ' ' + href + ' ' + VLC_ARGS
  } else if (argv.mplayer) {
    cmd = MPLAYER_EXEC + ' ' + href
  } else if (argv.mpv) {
    cmd = MPV_EXEC + ' ' + href
  } else if (argv.omx) {
    cmd = OMX_EXEC + ' ' + href
  }

  if (cmd) {
    player = cp.exec(cmd, function (err) {
      if (err) return errorAndExit(err)
      done()
    })
  }

  if (argv.airplay) {
    var airplay = require('airplay-js')
    airplay.createBrowser()
      .on('deviceOn', function (device) {
        device.play(href, 0, function () {})
      })
      .start()
    // TODO: handle case where user closes airplay. do same thing as when VLC is closed
  }

  if (argv.chromecast) {
    var chromecast = require('chromecast-js')
    new chromecast.Browser()
      .on('deviceOn', function (device) {
        device.connect()
        device.on('connected', function () {
          device.play(href)
        })
      })
  }

  if (argv.xbmc) {
    var xbmc = require('nodebmc')
    new xbmc.Browser()
      .on('deviceOn', function (device) {
          device.play(href, function () {})
      })
  }

  var hotswaps = 0
  torrent.on('hotswap', function () {
    hotswaps += 1
  })

  if (!argv.quiet) {
    process.stdout.write(new Buffer('G1tIG1sySg==', 'base64')) // clear for drawing

    setInterval(draw, 500)
  }

  function active (wire) {
    return !wire.peerChoking
  }

  function bytes (num) {
    return prettysize(num)
  }

  function draw () {
    var unchoked = wires.filter(active)
    var linesremaining = clivas.height
    var peerslisted = 0
    var speed = swarm.downloadSpeed()
    var estimatedSecondsRemaining = Math.max(0, torrent.length - swarm.downloaded) / (speed > 0 ? speed : -1)
    var estimate = moment.duration(estimatedSecondsRemaining, 'seconds').humanize()

    clivas.clear()

    if (playerName)
      clivas.line('{green:Streaming to} {bold:' + playerName + '}')
    if (server)
      clivas.line('{green:server running at} {bold:' + href + '}')

    clivas.line('')
    clivas.line('{green:downloading:} {bold:' + filename + '}')
    clivas.line(
      '{green:speed: }{bold:' + bytes(speed) + '/s}  ' +
      '{green:downloaded:} {bold:' + bytes(swarm.downloaded) + '}' +
      '/{bold:' + bytes(torrent.length) + '}  ' +
      '{green:uploaded:} {bold:' + bytes(swarm.uploaded) + '}  ' +
      '{green:peers:} {bold:' + unchoked.length + '/' + wires.length + '}  ' +
      '{green:hotswaps:} {bold:' + hotswaps + '}'
    )
    clivas.line(
      '{green:time remaining:} {bold:' + estimate + ' remaining}  ' +
      '{green:total time:} {bold:' + getRuntime() + 's}  ' +
      '{green:queued peers:} {bold:' + swarm.numQueued + '}  ' +
      '{green:blocked:} {bold:' + torrent.numBlockedPeers + '}'
    )
    clivas.line('{80:}')
    linesremaining -= 8

    var pieces = torrent.storage.pieces
    var piecesBar = []
    for(var i = 0; i < pieces.length; i++) {
      var piece = pieces[i]
      if (piece.verified || piece.blocksWritten === 0) {
        continue;
      }
      var bar = ''
      for(var j = 0; j < piece.blocks.length; j++) {
        bar += piece.blocks[j] ? '{green:█}' : '{red:█}';
      }
      clivas.line('{4+cyan:' + i + '} ' + bar);
      linesremaining -= 1
    }
    clivas.line('{80:}')
    linesremaining -= 1

    wires.every(function (wire) {
      var tags = []
      if (wire.peerChoking) tags.push('choked')
      clivas.line(
        '{25+magenta:' + wire.remoteAddress + '} {10:'+bytes(wire.downloaded)+'} ' +
        '{10+cyan:' + bytes(wire.downloadSpeed()) + '/s} ' +
        '{15+grey:' + tags.join(', ') + '}'
      )
      peerslisted++
      return linesremaining - peerslisted > 4
    })
    linesremaining -= peerslisted

    if (wires.length > peerslisted) {
      clivas.line('{80:}')
      clivas.line('... and '+(wires.length - peerslisted)+' more')
    }

    clivas.line('{80:}')
    clivas.flush(true)
  }
}
<commit_msg>display requests per wire<commit_after>#!/usr/bin/env node

var clivas = require('clivas')
var cp = require('child_process')
var fs = require('fs')
var minimist = require('minimist')
var moment = require('moment')
var networkAddress = require('network-address')
var path = require('path')
var prettysize = require('prettysize')
var WebTorrent = require('../')

process.title = 'WebTorrent'

process.on('exit', function (code) {
  if (code !== 0) {
    clivas.line('{red:ERROR:} If you think this is a bug in webtorrent, report it!')
    console.log('=====>                                               <=====')
    console.log('=====>  https://github.com/feross/webtorrent/issues  <=====')
    console.log('=====>                                               <=====')
  }
})

var argv = minimist(process.argv.slice(2), {
  alias: {
    p: 'port',
    b: 'blocklist',
    t: 'subtitles',
    l: 'list',
    i: 'index',
    o: 'out',
    q: 'quiet',
    h: 'help',
    v: 'version'
  },
  boolean: [ // options that are always boolean
    'airplay',
    'chromecast',
    'mplayer',
    'mpv',
    'vlc',
    'xbmc',
    'stdout',
    'list',
    'quiet',
    'help',
    'version'
  ],
  default: {
    port: 8000
  }
})

if (argv.version) {
  console.log(require('../package.json').version)
  return done()
}

var torrentId = argv._[0]

if (argv.help || !torrentId) {
  fs.readFileSync(path.join(__dirname, 'ascii-logo.txt'), 'utf8')
    .split('\n')
    .forEach(function (line) {
      clivas.line('{bold:' + line.substring(0, 20) + '}{red:' + line.substring(20) + '}')
    })

  console.log(function () {/*
  Usage:
      webtorrent <options> <torrent-uri>

      Download the torrent from:
          * magnet uri
          * http url to .torrent file
          * filesystem path to .torrent file
          * info hash (hex string)

  Streaming options:
      --airplay               Apple TV
      --chromecast            Chromecast
      --mplayer               MPlayer
      --mpv                   MPV
      --omx [jack]            omx [default: hdmi]
      --vlc                   VLC
      --xbmc                  XBMC
      --stdout                standard out (implies --quiet)

  Options:
      -o, --out [path]        set download destination [default: /tmp/webtorrent]
      -l, --list              list files in torrent (with indexes)
      -i, --index [index]     stream a particular file from torrent (by index)
      -p, --port [number]     change the http port [default: 8000]
      -b, --blocklist [path]  load blocklist file/http url
      -t, --subtitles [file]  load subtitles file
      -q, --quiet             don't show UI on stdout
      -v, --version           print the current version

  Please report bugs!  https://github.com/feross/webtorrent/issues

    */}.toString().split(/\n/).slice(1, -1).join('\n'))
  return done()
}

if (process.env.DEBUG || argv.stdout) {
  argv.quiet = argv.q = true
}

var VLC_ARGS = process.env.DEBUG
  ? '-q --play-and-exit'
  : '--play-and-exit --extraintf=http:logger --verbose=2 --file-logging --logfile=vlc-log.txt'
var MPLAYER_EXEC = 'mplayer -ontop -really-quiet -noidx -loop 0'
var MPV_EXEC = 'mpv --ontop --really-quiet --loop=no'
var OMX_EXEC = 'omxplayer -r -o ' + (typeof argv.omx === 'string')
  ? argv.omx
  : 'hdmi'

if (argv.subtitles) {
  VLC_ARGS += ' --sub-file=' + argv.subtitles
  MPLAYER_EXEC += ' -sub ' + argv.subtitles
  MPV_EXEC += ' --sub-file=' + argv.subtitles
  OMX_EXEC += ' --subtitles ' + argv.subtitles
}

function error (err) {
  clivas.line('{red:ERROR:} ' + (err.message || err))
}

function errorAndExit (err) {
  error(err)
  process.exit(1)
}

var started = Date.now()
function getRuntime () {
  return Math.floor((Date.now() - started) / 1000)
}

var client = new WebTorrent({
  blocklist: argv.blocklist
})
.on('error', errorAndExit)

if (!argv.out) { // If no output file has been specified
  process.on('SIGINT', remove)
  process.on('SIGTERM', remove)
}

function remove (cb) {
  process.removeListener('SIGINT', remove)
  process.removeListener('SIGTERM', remove)
  client.destroy(cb)
}

var torrent = client.add(torrentId, (argv.out ? { tmp: argv.out } : {}))

torrent.on('infoHash', function () {
  function updateMetadata () {
    var numPeers = torrent.swarm.numPeers
    clivas.clear()
    clivas.line('{green:fetching torrent metadata from} {bold:'+numPeers+'} {green:peers}')
  }

  if (!argv.quiet && !argv.list) {
    torrent.swarm.on('wire', updateMetadata)
    torrent.on('metadata', function () {
      torrent.swarm.removeListener('wire', updateMetadata)
    })
    updateMetadata()
  }
})

var filename, swarm, wires, server, serving

if (argv.list) torrent.once('ready', onReady)
else {
  server = torrent.createServer()
  server.listen(argv.port, function () {
    if (torrent.ready) onReady()
    else torrent.once('ready', onReady)
  }).once('connection', function () {
    serving = true
  })
}

function done () {
  if (!serving) {
    process.exit(0)
  }
}

function onReady () {
  filename = torrent.name
  swarm = torrent.swarm
  wires = torrent.swarm.wires

  if (argv.list) {
    torrent.files.forEach(function (file, i) {
      clivas.line('{3+bold:' + i + '} : {magenta:' + file.name + '}')
    })
    return done()
  }

  torrent.on('verifying', function (data) {
    if (argv.quiet) return
    clivas.clear()
    clivas.line(
      '{green:verifying existing torrent} {bold:'+Math.floor(data.percentDone)+'%} ' +
      '({bold:'+Math.floor(data.percentVerified)+'%} {green:passed verification})'
    )
  })

  torrent.on('done', function () {
    if (!argv.quiet) {
      // TODO: expose this data from bittorrent-swarm
      var numActiveWires = torrent.swarm.wires.reduce(function (num, wire) {
        return num + (wire.downloaded > 0)
      }, 0)
      clivas.line('torrent downloaded {green:successfully} from {bold:'+numActiveWires+'/'+torrent.swarm.wires.length+'} {green:peers} in {bold:'+getRuntime()+'s}!')
    }
    done()
  })

  var cmd, player
  var playerName = argv.airplay ? 'Airplay'
    : argv.chromecast ? 'Chromecast'
    : argv.xbmc ? 'XBMC'
    : argv.vlc ? 'VLC'
    : argv.mplayer ? 'MPlayer'
    : argv.mpv ? 'mpv'
    : argv.omx ? 'OMXPlayer'
    : null

  // if no index specified, use largest file
  var index = (typeof argv.index === 'number')
    ? argv.index
    : torrent.files.indexOf(torrent.files.reduce(function (a, b) {
      return a.length > b.length ? a : b
    }))
  var href = 'http://' + networkAddress() + ':' + argv.port + '/' + index

  if (playerName) torrent.files[index].select()
  if (argv.stdout) torrent.files[index].createReadStream().pipe(process.stdout)

  if (argv.vlc && process.platform === 'win32') {
    var registry = require('windows-no-runnable').registry
    var key
    if (process.arch === 'x64') {
      try {
        key = registry('HKLM/Software/Wow6432Node/VideoLAN/VLC')
      } catch (e) {}
    } else {
      try {
        key = registry('HKLM/Software/VideoLAN/VLC')
      } catch (err) {}
    }

    if (key) {
      var vlcPath = key.InstallDir.value + path.sep + 'vlc'
      VLC_ARGS = VLC_ARGS.split(' ')
      VLC_ARGS.unshift(href)
      cp.execFile(vlcPath, VLC_ARGS, function (err) {
        if (err) return errorAndExit(err)
        done()
      })
    }
  } else if (argv.vlc) {
    var root = '/Applications/VLC.app/Contents/MacOS/VLC'
    var home = (process.env.HOME || '') + root
    cmd = 'vlc ' + href + ' ' + VLC_ARGS + ' || ' +
      root + ' ' + href + ' ' + VLC_ARGS + ' || ' +
      home + ' ' + href + ' ' + VLC_ARGS
  } else if (argv.mplayer) {
    cmd = MPLAYER_EXEC + ' ' + href
  } else if (argv.mpv) {
    cmd = MPV_EXEC + ' ' + href
  } else if (argv.omx) {
    cmd = OMX_EXEC + ' ' + href
  }

  if (cmd) {
    player = cp.exec(cmd, function (err) {
      if (err) return errorAndExit(err)
      done()
    })
  }

  if (argv.airplay) {
    var airplay = require('airplay-js')
    airplay.createBrowser()
      .on('deviceOn', function (device) {
        device.play(href, 0, function () {})
      })
      .start()
    // TODO: handle case where user closes airplay. do same thing as when VLC is closed
  }

  if (argv.chromecast) {
    var chromecast = require('chromecast-js')
    new chromecast.Browser()
      .on('deviceOn', function (device) {
        device.connect()
        device.on('connected', function () {
          device.play(href)
        })
      })
  }

  if (argv.xbmc) {
    var xbmc = require('nodebmc')
    new xbmc.Browser()
      .on('deviceOn', function (device) {
          device.play(href, function () {})
      })
  }

  var hotswaps = 0
  torrent.on('hotswap', function () {
    hotswaps += 1
  })

  if (!argv.quiet) {
    process.stdout.write(new Buffer('G1tIG1sySg==', 'base64')) // clear for drawing

    setInterval(draw, 500)
  }

  function active (wire) {
    return !wire.peerChoking
  }

  function bytes (num) {
    return prettysize(num)
  }

  function draw () {
    var unchoked = wires.filter(active)
    var linesremaining = clivas.height
    var peerslisted = 0
    var speed = swarm.downloadSpeed()
    var estimatedSecondsRemaining = Math.max(0, torrent.length - swarm.downloaded) / (speed > 0 ? speed : -1)
    var estimate = moment.duration(estimatedSecondsRemaining, 'seconds').humanize()

    clivas.clear()

    if (playerName)
      clivas.line('{green:Streaming to} {bold:' + playerName + '}')
    if (server)
      clivas.line('{green:server running at} {bold:' + href + '}')

    clivas.line('')
    clivas.line('{green:downloading:} {bold:' + filename + '}')
    clivas.line(
      '{green:speed: }{bold:' + bytes(speed) + '/s}  ' +
      '{green:downloaded:} {bold:' + bytes(swarm.downloaded) + '}' +
      '/{bold:' + bytes(torrent.length) + '}  ' +
      '{green:uploaded:} {bold:' + bytes(swarm.uploaded) + '}  ' +
      '{green:peers:} {bold:' + unchoked.length + '/' + wires.length + '}  ' +
      '{green:hotswaps:} {bold:' + hotswaps + '}'
    )
    clivas.line(
      '{green:time remaining:} {bold:' + estimate + ' remaining}  ' +
      '{green:total time:} {bold:' + getRuntime() + 's}  ' +
      '{green:queued peers:} {bold:' + swarm.numQueued + '}  ' +
      '{green:blocked:} {bold:' + torrent.numBlockedPeers + '}'
    )
    clivas.line('{80:}')
    linesremaining -= 8

    var pieces = torrent.storage.pieces
    var piecesBar = []
    for(var i = 0; i < pieces.length; i++) {
      var piece = pieces[i]
      if (piece.verified || piece.blocksWritten === 0) {
        continue;
      }
      var bar = ''
      for(var j = 0; j < piece.blocks.length; j++) {
        bar += piece.blocks[j] ? '{green:█}' : '{red:█}';
      }
      clivas.line('{4+cyan:' + i + '} ' + bar);
      linesremaining -= 1
    }
    clivas.line('{80:}')
    linesremaining -= 1

    wires.every(function (wire) {
      var tags = []
      if (wire.peerChoking) tags.push('choked')
      var reqStats = wire.requests.map(function(req) {
          return req.piece;
      })
      clivas.line(
        '{25+magenta:' + wire.remoteAddress + '} {10:'+bytes(wire.downloaded)+'} ' +
        '{10+cyan:' + bytes(wire.downloadSpeed()) + '/s} ' +
        '{15+grey:' + tags.join(', ') + '}' +
        '{15+cyan:' + reqStats.join(' ') + '}'
      )
      peerslisted++
      return linesremaining - peerslisted > 4
    })
    linesremaining -= peerslisted

    if (wires.length > peerslisted) {
      clivas.line('{80:}')
      clivas.line('... and '+(wires.length - peerslisted)+' more')
    }

    clivas.line('{80:}')
    clivas.flush(true)
  }
}
<|endoftext|>


<commit_before>/*
 * Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of perseo-fe
 *
 * perseo-fe is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * perseo-fe is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with perseo-fe.
 * If not, see http://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with iot_support at tid dot es
 */

'use strict';

var should = require('should'),
    fs = require('fs'),
    clients = require('../utils/clients'),
    testEnv = require('../utils/testEnvironment');

describe('Notices', function() {
    beforeEach(testEnv.commonBeforeEach);
    afterEach(testEnv.commonAfterEach);

    describe('#GetVersion()', function() {
        it('should return version in package.json', function(done) {
            var packageObj = JSON.parse(fs.readFileSync('package.json')),
                body, obj;
            clients.GetVersion(function(error, data) {
                should.not.exist(error);
                data.should.have.property('statusCode', 200);
                data.should.have.property('body');
                body = data.body;
                body.should.have.property('data');
                obj = body.data;
                obj.should.have.property('name', packageObj.name);
                obj.should.have.property('version', packageObj.version);
                return done();
            });
        });
    });
});

<commit_msg>Fix description text for test<commit_after>/*
 * Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of perseo-fe
 *
 * perseo-fe is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * perseo-fe is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with perseo-fe.
 * If not, see http://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with iot_support at tid dot es
 */

'use strict';

var should = require('should'),
    fs = require('fs'),
    clients = require('../utils/clients'),
    testEnv = require('../utils/testEnvironment');

describe('Version', function() {
    beforeEach(testEnv.commonBeforeEach);
    afterEach(testEnv.commonAfterEach);

    describe('#GetVersion()', function() {
        it('should return version in package.json', function(done) {
            var packageObj = JSON.parse(fs.readFileSync('package.json')),
                body, obj;
            clients.GetVersion(function(error, data) {
                should.not.exist(error);
                data.should.have.property('statusCode', 200);
                data.should.have.property('body');
                body = data.body;
                body.should.have.property('data');
                obj = body.data;
                obj.should.have.property('name', packageObj.name);
                obj.should.have.property('version', packageObj.version);
                return done();
            });
        });
    });
});

<|endoftext|>