<commit_before>var kafka = require('kafka-node'),
    config = require('../config'),
    consumers = {},
    log = require('../logger'),
    logger = log.logger,
    uuid = require('uuid');

var get = function (group, id, callback) {
        if (arguments.length < 1) {
            callback(new Error('Group and Instance ID or ID required.'));
        }

        if (typeof id === 'string'){
            group += '/' + id;
        }
        else if (typeof id === 'function') {
            callback = id;
        }

        var consumer = consumers[group];
        if (!consumer) {
            return callback( {message: 'consumer not found'}, null);
        }

        if (callback) {
            return callback(null, consumer);
        }

        return consumer;
    },

    add = function (consumer, callback) {
        consumer.instanceId = consumer.instanceId || uuid.v4();
        consumer.id = consumer.group + '/' + consumer.instanceId;
        if (!!consumers[consumer.id]) {
            callback(new Error('Consumer with ID ' + consumer.id + ' already exists.'));
        }

        consumer.autoOffsetReset = consumer.autoOffsetReset || 'largest';
        consumer.autoCommitEnable = typeof consumer.autoCommitEnable === 'undefined' ? config.consumer.autoCommitEnable : consumer.autoCommitEnable;
        consumer.instance = undefined;
        consumer.topics = [];
        consumer.messages = [];
        consumer.created = new Date();
        consumer.lastPoll = Date.now();
        consumer.lock = false;
        consumer.byteSize = 0;
        consumer.offsetMap = [];

        consumers[consumer.id] = consumer;
        logger.debug({consumer: consumer}, 'lib/consumerManager : consumer added');
        callback(null, consumer);
    },

    createConsumerInstance = function (consumer, topic) {
        if (consumer.lock) {
            logger.debug('lib/consumerManager : consumer is locked, return as there is already an instance or its being recreated');
            return;
        }

        logger.debug('lib/consumerManager : creating a kafka client');
        var client = new kafka.Client(config.kafka.zkConnect, config.kafka.clientId);
        logger.debug('lib/consumerManager : creating a new consumer');
        consumer.instance = new kafka.HighLevelConsumer(client, [{
            topic: topic
        }], {
            groupId: consumer.group,
            // Auto commit config
            autoCommit: false,
            // The max wait time is the maximum amount of time in milliseconds to block waiting if insufficient data is available at the time the request is issued, default 100ms
            fetchMaxWaitMs: 100,
            // This is the minimum number of bytes of messages that must be available to give a response, default 1 byte
            fetchMinBytes: 1,
            // The maximum bytes to include in the message set for this partition. This helps bound the size of the response.
            fetchMaxBytes: 4 * 1024 * 1024, // 4MB
            // If set true, consumer will fetch message from the given offset in the payloads
            fromOffset: false,
            // If set to 'buffer', values will be returned as raw buffer objects.
            encoding: 'utf8'
        });

        logger.debug('lib/consumerManager : consumer created');
        consumer.instance.on('message', function (m) {
            logger.trace({messages: consumer.messages, message: m}, 'lib/consumerManager : consumer message received');
            consumer.messages.push(m);
            consumer.byteSize += Buffer.byteLength(m.value);

            if (consumer.byteSize > (16 * 1024 * 1024)) {
                logger.info('lib/consumerManager : consumer message queue greater than acceptable size, pausing consumer');
                consumer.instance.pause();
            }
        });

        consumer.instance.on('rebalancing', function(event){
            logger.warn('lib/consumerManager : Consumer rebalancing');
            consumer.messages = [];
        });

        consumer.instance.on('error', function (e) {
            logger.error({ error: e, consumer: consumer.id }, 'lib/consumerManager : Error in consumer instance. Closing and recreating...');
            if (!consumer.lock) {
                consumer.lock = true;
                logger.debug({ consumer: consumer.id }, 'lock enabled on consumer');
                consumer.instance.close(false, function () {
                    setTimeout(function () {
                        logger.info({consumer: consumer.id}, 'lib/consumerManager : Recreating consumer');
                        createConsumerInstance(consumer, topic);
                        consumer.lock = false;
                        logger.debug({ consumer: consumer.id }, 'lock released on consumer');
                    }, 1000);
                });
            }
        });

        consumer.instance.on('offsetOutOfRange', function (e) {
            logger.warn({ error: e }, 'lib/consumerManager : Received alert for offset out of range.');
        });
    },

    findOffsetInMap = function(map, searchItem) {
        logger.trace({map : map}, 'searching the offset map');
        return map.find(function(item) {
            return  item.topic === searchItem.topic &&
                    item.partition === searchItem.partition;
        });
    },

    createOffsetItem = function(message) {
        // note : metadata is an arbitrary value for kafka-node clients
        return {
            topic : message.topic || '',
            partition: message.partition || 0,
            offset : message.offset || 0,
            metadata : 'm'
        };
    },

    createOffsetMap = function(messages) {
        return messages.reduce(function(map, message){
            logger.trace(message, 'creating offset map for message');
            var offsetItem = findOffsetInMap(map, message);

            logger.trace({offsetItem: offsetItem || 'nothing'}, 'offset item from map');
            if (offsetItem === undefined) {
                map.push(createOffsetItem(message));
            } else {
                if (message.offset > offsetItem.offset) {
                    offsetItem.offset = message.offset;
                }
            }
            return map;
        }, []);
    },

    getMessages = function (consumer, callback) {
        consumer.lastPoll = Date.now();

logger.trace({buffer: consumer.messages}, 'consumer message buffer');
        var messages = consumer.messages.splice(0, consumer.messages.length);
        consumer.offsetMap = createOffsetMap(messages);

        if (messages.length === 0) {
            return callback(null, []);
        }

        if (consumer.autoCommitEnable) {
            logger.trace({ consumer: consumer.id }, 'lib/consumerManager : Autocommit.');
            consumer.instance.commit(true);
        }

        if (consumer.instance.paused) {
            consumer.instance.resume();
        }

        logger.trace({ consumer: consumer.id }, 'lib/consumerManager : returning ' + messages.length + ' messages.');
        callback(null, messages.map(function (m) {
            return {
                topic: m.topic,
                partition: m.partition,
                offset: m.offset,
                key: m.key.toString(),
                value: m.value
            };
        }));
    },

    commitOffsets = function(consumer, cb) {
        consumer.instance.client.sendOffsetCommitRequest(
            consumer.groupid,
            consumer.offsetMap.map(function(item){
                item.fetchMaxBytes = consumer.options.fetchMaxBytes;
                return item;
            }),
            callback);
    },

    deleteConsumer = function (consumer, cb) {
        logger.debug({ consumer: consumer.id }, 'lib/consumerManager : Removing consumer from set.');
        delete consumers[consumer.id];
        logger.debug({ consumer: consumer.id }, 'lib/consumerManager : Closing consumer...');
        if (!!consumer.instance) {
            consumer.instance.close(false, function () {
                logger.debug({ consumer: consumer.id }, 'lib/consumerManager : Consumer closed.');
                if (!!cb) cb();
            });
        }
    },

    timeoutConsumers = function () {
        logger.debug('lib/consumerManager : Looking for timed-out consumers.');
        var timeoutTime = Date.now() - config.consumer.timeoutMs;
        for (var i in consumers) {
            var consumer = consumers[i];
            if (consumer.instance && consumer.lastPoll < timeoutTime) {
                logger.debug({ consumer : consumer.id }, 'lib/consumerManager : Consumer timed-out.');
                deleteConsumer(consumer);
            }
        }
        logger.debug('lib/consumerManager : Done looking for timed-out consumers.');
    };

module.exports = {
    get: get,
    add: add,
    createInstance: createConsumerInstance,
    getMessages: getMessages,
    commitOffsets: commitOffsets,
    delete: deleteConsumer,
    timeout: timeoutConsumers
};
<commit_msg>tabbing<commit_after>var kafka = require('kafka-node'),
    config = require('../config'),
    consumers = {},
    log = require('../logger'),
    logger = log.logger,
    uuid = require('uuid');

var get = function (group, id, callback) {
        if (arguments.length < 1) {
            callback(new Error('Group and Instance ID or ID required.'));
        }

        if (typeof id === 'string'){
            group += '/' + id;
        }
        else if (typeof id === 'function') {
            callback = id;
        }

        var consumer = consumers[group];
        if (!consumer) {
            return callback( {message: 'consumer not found'}, null);
        }

        if (callback) {
            return callback(null, consumer);
        }

        return consumer;
    },

    add = function (consumer, callback) {
        consumer.instanceId = consumer.instanceId || uuid.v4();
        consumer.id = consumer.group + '/' + consumer.instanceId;
        if (!!consumers[consumer.id]) {
            callback(new Error('Consumer with ID ' + consumer.id + ' already exists.'));
        }

        consumer.autoOffsetReset = consumer.autoOffsetReset || 'largest';
        consumer.autoCommitEnable = typeof consumer.autoCommitEnable === 'undefined' ? config.consumer.autoCommitEnable : consumer.autoCommitEnable;
        consumer.instance = undefined;
        consumer.topics = [];
        consumer.messages = [];
        consumer.created = new Date();
        consumer.lastPoll = Date.now();
        consumer.lock = false;
        consumer.byteSize = 0;
        consumer.offsetMap = [];

        consumers[consumer.id] = consumer;
        logger.debug({consumer: consumer}, 'lib/consumerManager : consumer added');
        callback(null, consumer);
    },

    createConsumerInstance = function (consumer, topic) {
        if (consumer.lock) {
            logger.debug('lib/consumerManager : consumer is locked, return as there is already an instance or its being recreated');
            return;
        }

        logger.debug('lib/consumerManager : creating a kafka client');
        var client = new kafka.Client(config.kafka.zkConnect, config.kafka.clientId);
        logger.debug('lib/consumerManager : creating a new consumer');
        consumer.instance = new kafka.HighLevelConsumer(client, [{
            topic: topic
        }], {
            groupId: consumer.group,
            // Auto commit config
            autoCommit: false,
            // The max wait time is the maximum amount of time in milliseconds to block waiting if insufficient data is available at the time the request is issued, default 100ms
            fetchMaxWaitMs: 100,
            // This is the minimum number of bytes of messages that must be available to give a response, default 1 byte
            fetchMinBytes: 1,
            // The maximum bytes to include in the message set for this partition. This helps bound the size of the response.
            fetchMaxBytes: 4 * 1024 * 1024, // 4MB
            // If set true, consumer will fetch message from the given offset in the payloads
            fromOffset: false,
            // If set to 'buffer', values will be returned as raw buffer objects.
            encoding: 'utf8'
        });

        logger.debug('lib/consumerManager : consumer created');
        consumer.instance.on('message', function (m) {
            logger.trace({messages: consumer.messages, message: m}, 'lib/consumerManager : consumer message received');
            consumer.messages.push(m);
            consumer.byteSize += Buffer.byteLength(m.value);

            if (consumer.byteSize > (16 * 1024 * 1024)) {
                logger.info('lib/consumerManager : consumer message queue greater than acceptable size, pausing consumer');
                consumer.instance.pause();
            }
        });

        consumer.instance.on('rebalancing', function(event){
            logger.warn('lib/consumerManager : Consumer rebalancing');
            consumer.messages = [];
        });

        consumer.instance.on('error', function (e) {
            logger.error({ error: e, consumer: consumer.id }, 'lib/consumerManager : Error in consumer instance. Closing and recreating...');
            if (!consumer.lock) {
                consumer.lock = true;
                logger.debug({ consumer: consumer.id }, 'lock enabled on consumer');
                consumer.instance.close(false, function () {
                    setTimeout(function () {
                        logger.info({consumer: consumer.id}, 'lib/consumerManager : Recreating consumer');
                        createConsumerInstance(consumer, topic);
                        consumer.lock = false;
                        logger.debug({ consumer: consumer.id }, 'lock released on consumer');
                    }, 1000);
                });
            }
        });

        consumer.instance.on('offsetOutOfRange', function (e) {
            logger.warn({ error: e }, 'lib/consumerManager : Received alert for offset out of range.');
        });
    },

    findOffsetInMap = function(map, searchItem) {
        logger.trace({map : map}, 'searching the offset map');
        return map.find(function(item) {
            return  item.topic === searchItem.topic &&
                    item.partition === searchItem.partition;
        });
    },

    createOffsetItem = function(message) {
        // note : metadata is an arbitrary value for kafka-node clients
        return {
            topic : message.topic || '',
            partition: message.partition || 0,
            offset : message.offset || 0,
            metadata : 'm'
        };
    },

    createOffsetMap = function(messages) {
        return messages.reduce(function(map, message){
            logger.trace(message, 'creating offset map for message');
            var offsetItem = findOffsetInMap(map, message);

            logger.trace({offsetItem: offsetItem || 'nothing'}, 'offset item from map');
            if (offsetItem === undefined) {
                map.push(createOffsetItem(message));
            } else {
                if (message.offset > offsetItem.offset) {
                    offsetItem.offset = message.offset;
                }
            }
            return map;
        }, []);
    },

    getMessages = function (consumer, callback) {
        consumer.lastPoll = Date.now();

        logger.trace({buffer: consumer.messages}, 'consumer message buffer');
        var messages = consumer.messages.splice(0, consumer.messages.length);
        consumer.offsetMap = createOffsetMap(messages);

        if (messages.length === 0) {
            return callback(null, []);
        }

        if (consumer.autoCommitEnable) {
            logger.trace({ consumer: consumer.id }, 'lib/consumerManager : Autocommit.');
            consumer.instance.commit(true);
        }

        if (consumer.instance.paused) {
            consumer.instance.resume();
        }

        logger.trace({ consumer: consumer.id }, 'lib/consumerManager : returning ' + messages.length + ' messages.');
        callback(null, messages.map(function (m) {
            return {
                topic: m.topic,
                partition: m.partition,
                offset: m.offset,
                key: m.key.toString(),
                value: m.value
            };
        }));
    },

    commitOffsets = function(consumer, cb) {
        consumer.instance.client.sendOffsetCommitRequest(
            consumer.groupid,
            consumer.offsetMap.map(function(item){
                item.fetchMaxBytes = consumer.options.fetchMaxBytes;
                return item;
            }),
            callback);
    },

    deleteConsumer = function (consumer, cb) {
        logger.debug({ consumer: consumer.id }, 'lib/consumerManager : Removing consumer from set.');
        delete consumers[consumer.id];
        logger.debug({ consumer: consumer.id }, 'lib/consumerManager : Closing consumer...');
        if (!!consumer.instance) {
            consumer.instance.close(false, function () {
                logger.debug({ consumer: consumer.id }, 'lib/consumerManager : Consumer closed.');
                if (!!cb) cb();
            });
        }
    },

    timeoutConsumers = function () {
        logger.debug('lib/consumerManager : Looking for timed-out consumers.');
        var timeoutTime = Date.now() - config.consumer.timeoutMs;
        for (var i in consumers) {
            var consumer = consumers[i];
            if (consumer.instance && consumer.lastPoll < timeoutTime) {
                logger.debug({ consumer : consumer.id }, 'lib/consumerManager : Consumer timed-out.');
                deleteConsumer(consumer);
            }
        }
        logger.debug('lib/consumerManager : Done looking for timed-out consumers.');
    };

module.exports = {
    get: get,
    add: add,
    createInstance: createConsumerInstance,
    getMessages: getMessages,
    commitOffsets: commitOffsets,
    delete: deleteConsumer,
    timeout: timeoutConsumers
};
<|endoftext|>


<commit_before>// @flow
/**
 * Part of GDL gdl-frontend.
 * Copyright (C) 2018 GDL
 *
 * See LICENSE
 */

import * as React from 'react';
import { Trans } from '@lingui/react';
import styled from 'react-emotion';
import { Button, Card, CardContent, Typography } from '@material-ui/core';

import { logEvent } from '../../lib/analytics';
import ReadingLevelTrans from '../../components/ReadingLevelTrans';
import type {
  Book,
  Language,
  FeaturedContent,
  ReadingLevel,
  Category
} from '../../types';
import Layout from '../../components/Layout';
import Main from '../../components/Layout/Main';
import { Container, View } from '../../elements';
import {
  NavContextBar,
  CategoryNavigation
} from '../../components/NavContextBar';
import Head from '../../components/Head';
import BookList from '../../components/BookList';
import { colors, spacing } from '../../style/theme';
import media from '../../style/media';
import { flexCenter } from '../../style/flex';

type Props = {|
  featuredContent: Array<FeaturedContent>,
  newArrivals: { results: Array<Book>, language: Language },
  levels: Array<ReadingLevel>,
  booksByLevel: Array<{ results: Array<Book> }>,
  categories: Array<Category>,
  category: Category
|};

const Banner = styled('div')`
  background-image: ${p => (p.src ? `url(${p.src})` : 'none')};
  background-size: cover;
  position: relative;
  display: flex;
  padding: 15px;
  justify-content: center;
  ${media.mobile`
    height: 210px;
  `} ${media.tablet`
    min-height: 390px;
    padding: 20px;
    justify-content: flex-end;
  `};
`;

const HeroCovertitle = styled('div')`
  position: absolute;
  top: 0;
  left: 0;
  background: rgba(0, 0, 0, 0.5);
  margin: 0;
  padding: 3px 12px;
`;

const HeroCardMobile = styled(Card)`
  ${flexCenter};
  position: relative;
  margin-top: -50px;
  margin-left: 15px;
  margin-right: 15px;
  ${media.tablet`
    display: none;
  `};
`;

const HeroCardTablet = styled(Card)`
  ${flexCenter};
  max-width: 375px;
  ${media.mobile`
    display: none;
  `};
`;

export default class HomePage extends React.Component<
  Props,
  { showLanguageMenu: boolean }
> {
  state = {
    showLanguageMenu: false
  };

  render() {
    const {
      category,
      featuredContent,
      levels,
      booksByLevel,
      newArrivals,
      categories
    } = this.props;

    const featuredForChosenCategory = featuredContent.filter(
      // $FlowFixMe...
      f => f.category && f.category.name === category
    );
    const featured =
      featuredForChosenCategory.length > 0
        ? featuredForChosenCategory[0]
        : featuredContent[0];

    const cardContent = (
      <View alignItems="center">
        <Typography
          lang={featured.language.code}
          align="center"
          variant="headline"
          component="h2"
          gutterBottom
        >
          {featured.title}
        </Typography>
        <Typography lang={featured.language.code} align="center" paragraph>
          {featured.description}
        </Typography>
        <Button
          onClick={() => logEvent('Navigation', 'Featured', featured.title)}
          href={featured.link}
          variant="raised"
          color="primary"
          size="large"
        >
          <Trans>More</Trans>
        </Button>
      </View>
    );

    const languageCode = newArrivals.language.code;

    return (
      <Layout category={category} wrapWithMain={false}>
        <Head image={featured.imageUrl} />
        <NavContextBar>
          <CategoryNavigation
            category={category}
            categories={categories}
            languageCode={languageCode}
          />
        </NavContextBar>
        <Main>
          <Banner src={featured.imageUrl}>
            <HeroCovertitle>
              <Typography
                component="h1"
                variant="title"
                css={{ color: colors.base.white }}
              >
                <Trans>Featured</Trans>
              </Typography>
            </HeroCovertitle>
            <HeroCardTablet>
              <CardContent>{cardContent}</CardContent>
            </HeroCardTablet>
          </Banner>
          <HeroCardMobile>
            <CardContent>{cardContent}</CardContent>
          </HeroCardMobile>

          {levels.map((level, index) => (
            <View {...bookListViewStyle} key={level}>
              <Container width="100%">
                <BookList
                  heading={<ReadingLevelTrans readingLevel={level} />}
                  browseLinkProps={{
                    lang: languageCode,
                    readingLevel: level,
                    category: category
                  }}
                  books={booksByLevel[index].results}
                />
              </Container>
            </View>
          ))}

          <View {...bookListViewStyle}>
            <Container width="100%">
              <BookList
                heading={<Trans>New arrivals</Trans>}
                browseLinkProps={{
                  lang: languageCode,
                  sort: '-arrivalDate',
                  category: category
                }}
                books={newArrivals.results}
              />
            </Container>
          </View>
        </Main>
      </Layout>
    );
  }
}

const bookListViewStyle = {
  py: spacing.medium,
  borderBottom: `solid 1px ${colors.base.grayLight}`
};
<commit_msg>Explicit widths makes IE 11 wrap text<commit_after>// @flow
/**
 * Part of GDL gdl-frontend.
 * Copyright (C) 2018 GDL
 *
 * See LICENSE
 */

import * as React from 'react';
import { Trans } from '@lingui/react';
import styled from 'react-emotion';
import { Button, Card, CardContent, Typography } from '@material-ui/core';

import { logEvent } from '../../lib/analytics';
import ReadingLevelTrans from '../../components/ReadingLevelTrans';
import type {
  Book,
  Language,
  FeaturedContent,
  ReadingLevel,
  Category
} from '../../types';
import Layout from '../../components/Layout';
import Main from '../../components/Layout/Main';
import { Container, View } from '../../elements';
import {
  NavContextBar,
  CategoryNavigation
} from '../../components/NavContextBar';
import Head from '../../components/Head';
import BookList from '../../components/BookList';
import { colors, spacing } from '../../style/theme';
import media from '../../style/media';
import { flexCenter } from '../../style/flex';

type Props = {|
  featuredContent: Array<FeaturedContent>,
  newArrivals: { results: Array<Book>, language: Language },
  levels: Array<ReadingLevel>,
  booksByLevel: Array<{ results: Array<Book> }>,
  categories: Array<Category>,
  category: Category
|};

const Banner = styled('div')`
  background-image: ${p => (p.src ? `url(${p.src})` : 'none')};
  background-size: cover;
  position: relative;
  display: flex;
  padding: 15px;
  justify-content: center;
  ${media.mobile`
    height: 210px;
  `} ${media.tablet`
    min-height: 390px;
    padding: 20px;
    justify-content: flex-end;
  `};
`;

const HeroCovertitle = styled('div')`
  position: absolute;
  top: 0;
  left: 0;
  background: rgba(0, 0, 0, 0.5);
  margin: 0;
  padding: 3px 12px;
`;

const HeroCardMobile = styled(Card)`
  ${flexCenter};
  position: relative;
  margin-top: -50px;
  margin-left: 15px;
  margin-right: 15px;
  ${media.tablet`
    display: none;
  `};
`;

const HeroCardTablet = styled(Card)`
  ${flexCenter};
  max-width: 375px;
  ${media.mobile`
    display: none;
  `};
`;

export default class HomePage extends React.Component<
  Props,
  { showLanguageMenu: boolean }
> {
  state = {
    showLanguageMenu: false
  };

  render() {
    const {
      category,
      featuredContent,
      levels,
      booksByLevel,
      newArrivals,
      categories
    } = this.props;

    const featuredForChosenCategory = featuredContent.filter(
      // $FlowFixMe...
      f => f.category && f.category.name === category
    );
    const featured =
      featuredForChosenCategory.length > 0
        ? featuredForChosenCategory[0]
        : featuredContent[0];

    const cardContent = (
      // Specifying width here makes text in IE11 wrap
      <View alignItems="center" style={{ width: '100%' }}>
        <Typography
          lang={featured.language.code}
          align="center"
          variant="headline"
          component="h2"
          gutterBottom
          // Specifying width here makes text in IE11 wrap
          style={{ width: '100%' }}
        >
          {featured.title}
        </Typography>
        <Typography
          lang={featured.language.code}
          align="center"
          paragraph
          // Specifying width here makes text in IE11 wrap
          style={{ width: '100%' }}
        >
          {featured.description}
        </Typography>
        <Button
          onClick={() => logEvent('Navigation', 'Featured', featured.title)}
          href={featured.link}
          variant="raised"
          color="primary"
          size="large"
        >
          <Trans>More</Trans>
        </Button>
      </View>
    );

    const languageCode = newArrivals.language.code;

    return (
      <Layout category={category} wrapWithMain={false}>
        <Head image={featured.imageUrl} />
        <NavContextBar>
          <CategoryNavigation
            category={category}
            categories={categories}
            languageCode={languageCode}
          />
        </NavContextBar>
        <Main>
          <Banner src={featured.imageUrl}>
            <HeroCovertitle>
              <Typography
                component="h1"
                variant="title"
                css={{ color: colors.base.white }}
              >
                <Trans>Featured</Trans>
              </Typography>
            </HeroCovertitle>
            <HeroCardTablet>
              {/* Specifying width here makes text in IE11 wrap*/}
              <CardContent style={{ width: '100%' }}>{cardContent}</CardContent>
            </HeroCardTablet>
          </Banner>
          <HeroCardMobile>
            <CardContent>{cardContent}</CardContent>
          </HeroCardMobile>

          {levels.map((level, index) => (
            <View {...bookListViewStyle} key={level}>
              <Container width="100%">
                <BookList
                  heading={<ReadingLevelTrans readingLevel={level} />}
                  browseLinkProps={{
                    lang: languageCode,
                    readingLevel: level,
                    category: category
                  }}
                  books={booksByLevel[index].results}
                />
              </Container>
            </View>
          ))}

          <View {...bookListViewStyle}>
            <Container width="100%">
              <BookList
                heading={<Trans>New arrivals</Trans>}
                browseLinkProps={{
                  lang: languageCode,
                  sort: '-arrivalDate',
                  category: category
                }}
                books={newArrivals.results}
              />
            </Container>
          </View>
        </Main>
      </Layout>
    );
  }
}

const bookListViewStyle = {
  py: spacing.medium,
  borderBottom: `solid 1px ${colors.base.grayLight}`
};
<|endoftext|>


<commit_before>"use strict";

import utils from "./utils";
import path from "path";

let extensions = {
    javascript: ".js",
    typescript: ".ts",
    css: ".css",
    sass: ".scss",
    html: ".html",
    sourcemap: ".map",
    png: ".png",
    jpg: ".jpg",
    jpeg: ".jpeg",
    gif: ".gif",
    svg: ".svg"
};

let folders = {
    root: ".",
    dist: "./dist",
    temp: "./.tmp",
    app: "./app",
    styles: "./styles",
    scripts: "./scripts",
    images: "./images",
    typings: "./typings",
    nodeModules: "./node_modules",
    jspmPackages: "./jspm_packages"
};

let globs = {
    any: "/**/*",
    scripts: {
        javascript: "/**/*" + extensions.javascript,
        typescript: "/**/*" + extensions.typescript
    },
    styles: {
        css: "/**/*" + extensions.css,
        sass: "/**/*" + extensions.sass,
        vendor: path.join(folders.styles, "/vendor" + "{" + extensions.sass + "," + extensions.css + "}")
    },
    images: path.join(folders.images, "/**/*" + "{" + extensions.png + "," + extensions.jpg + "," + extensions.jpeg + "," + extensions.gif + "," + extensions.svg + "}"),
    html: "/**/*" + extensions.html,
    sourcemaps: "/**/*" + extensions.sourcemap
};

let files = {
    any: "*",
    packageJSON: path.join(folders.root, "/package.json"),
    typeScriptDefinitions: path.join(folders.typings, globs.scripts.typescript),
    systemjsConfigDefault: "jspm.conf.js"
};

let webServerFolders = {
    dev: [
        // the order IS important. Folders above have precedence
        folders.root, // necessary to have jspm_packages & jspm config file without needing a copy step
        folders.temp, // before app so that ES5 code emitted by TypeScript/Babel takes precedence over ES2015 code that might be written in the app folder
        folders.app
    ],
    dist: [
        folders.dist
    ]
};

let webServerNames = {
    dev: "MDW_DEV",
    dist: "MDW_DIST"
};

let finalJsBundleName = "bundle.min.js";

let javascript = {
    src: [
        path.join(folders.app, globs.scripts.javascript)
    ],
    srcDist: path.join(folders.temp, "/core/boot.js"),
    dest: folders.temp,
    destDist: path.join(folders.dist, "/" + finalJsBundleName),
    finalJsBundlePath: finalJsBundleName
};

let typescript = {
    srcAppOnly: [
        path.join(folders.app, globs.scripts.typescript)
    ],
    dest: folders.temp // JavaScript code is emitted in the temp folder
};

let finalCSSBundleName = "bundle.min.css";
let finalCSSVendorBundleName = "vendor.min.css";

let styles = {
    src: [
        path.join(folders.app, globs.styles.css),
        path.join(folders.app, globs.styles.sass)
    ],
    srcVendorOnly: [
        path.join(folders.app, globs.styles.vendor)
    ],
    srcWithoutVendor: [
        path.join(folders.app, globs.styles.css),
        path.join(folders.app, globs.styles.sass),
        utils.exclude(path.join(folders.app, globs.styles.vendor))
    ],
    dest: folders.temp, // for DEV
    destFiles: path.join(folders.temp, globs.styles.css), // for DEV
    destDist: folders.dist, // for PROD
    finalCssBundleFilename: finalCSSBundleName,
    finalCssBundlePath: finalCSSBundleName,
    finalVendorCssBundleFilename: finalCSSVendorBundleName,
    finalVendorCssBundlePath: finalCSSVendorBundleName
};

let images = {
    src: [
        path.join(folders.app, globs.images)
    ],
    dest: path.join(folders.dist, folders.images)
};

let html = {
    src: [
        path.join(folders.app, globs.html)
    ],
    dest: folders.dist
};

let copy = {
    src: [
        path.join(folders.app, globs.any),

        // ignore stuff handled by the other tasks
        utils.exclude(path.join(folders.app, globs.html)),
        utils.exclude(path.join(folders.app, globs.styles.css)),
        utils.exclude(path.join(folders.app, globs.styles.sass)),
        utils.exclude(path.join(folders.app, globs.scripts.javascript)),
        utils.exclude(path.join(folders.app, globs.scripts.typescript))
    ],
    dest: folders.dist
};

let autoprefixerBrowsers = [
    "ie >= 10",
    "ie_mob >= 10",
    "ff >= 30",
    "chrome >= 34",
    "safari >= 7",
    "opera >= 23",
    "ios >= 7",
    "android >= 4.4",
    "bb >= 10"
];

let minifyCss = { // https://www.npmjs.com/package/gulp-minify
    keepBreaks: false, // no problem here
    keepSpecialComments: true, // necessary for licensing
    compatibility: false, // no problem here
    aggressiveMerging: false // necessary because it breaks PureCSS
};

export default {
    extensions,
    folders,
    globs,
    files,
    javascript,
    typescript,
    styles,
    images,
    html,
    copy,
    autoprefixerBrowsers,
    minifyCss,
    webServerFolders,
    webServerNames
};
<commit_msg>Add TSX support for watches<commit_after>"use strict";

import utils from "./utils";
import path from "path";

let extensions = {
    javascript: ".js",
    typescript: ".{ts,tsx}",
    css: ".css",
    sass: ".scss",
    html: ".html",
    sourcemap: ".map",
    png: ".png",
    jpg: ".jpg",
    jpeg: ".jpeg",
    gif: ".gif",
    svg: ".svg"
};

let folders = {
    root: ".",
    dist: "./dist",
    temp: "./.tmp",
    app: "./app",
    styles: "./styles",
    scripts: "./scripts",
    images: "./images",
    typings: "./typings",
    nodeModules: "./node_modules",
    jspmPackages: "./jspm_packages"
};

let globs = {
    any: "/**/*",
    scripts: {
        javascript: "/**/*" + extensions.javascript,
        typescript: "/**/*" + extensions.typescript
    },
    styles: {
        css: "/**/*" + extensions.css,
        sass: "/**/*" + extensions.sass,
        vendor: path.join(folders.styles, "/vendor" + "{" + extensions.sass + "," + extensions.css + "}")
    },
    images: path.join(folders.images, "/**/*" + "{" + extensions.png + "," + extensions.jpg + "," + extensions.jpeg + "," + extensions.gif + "," + extensions.svg + "}"),
    html: "/**/*" + extensions.html,
    sourcemaps: "/**/*" + extensions.sourcemap
};

let files = {
    any: "*",
    packageJSON: path.join(folders.root, "/package.json"),
    typeScriptDefinitions: path.join(folders.typings, globs.scripts.typescript),
    systemjsConfigDefault: "jspm.conf.js"
};

let webServerFolders = {
    dev: [
        // the order IS important. Folders above have precedence
        folders.root, // necessary to have jspm_packages & jspm config file without needing a copy step
        folders.temp, // before app so that ES5 code emitted by TypeScript/Babel takes precedence over ES2015 code that might be written in the app folder
        folders.app
    ],
    dist: [
        folders.dist
    ]
};

let webServerNames = {
    dev: "MDW_DEV",
    dist: "MDW_DIST"
};

let finalJsBundleName = "bundle.min.js";

let javascript = {
    src: [
        path.join(folders.app, globs.scripts.javascript)
    ],
    srcDist: path.join(folders.temp, "/core/boot.js"),
    dest: folders.temp,
    destDist: path.join(folders.dist, "/" + finalJsBundleName),
    finalJsBundlePath: finalJsBundleName
};

let typescript = {
    srcAppOnly: [
        path.join(folders.app, globs.scripts.typescript)
    ],
    dest: folders.temp // JavaScript code is emitted in the temp folder
};

let finalCSSBundleName = "bundle.min.css";
let finalCSSVendorBundleName = "vendor.min.css";

let styles = {
    src: [
        path.join(folders.app, globs.styles.css),
        path.join(folders.app, globs.styles.sass)
    ],
    srcVendorOnly: [
        path.join(folders.app, globs.styles.vendor)
    ],
    srcWithoutVendor: [
        path.join(folders.app, globs.styles.css),
        path.join(folders.app, globs.styles.sass),
        utils.exclude(path.join(folders.app, globs.styles.vendor))
    ],
    dest: folders.temp, // for DEV
    destFiles: path.join(folders.temp, globs.styles.css), // for DEV
    destDist: folders.dist, // for PROD
    finalCssBundleFilename: finalCSSBundleName,
    finalCssBundlePath: finalCSSBundleName,
    finalVendorCssBundleFilename: finalCSSVendorBundleName,
    finalVendorCssBundlePath: finalCSSVendorBundleName
};

let images = {
    src: [
        path.join(folders.app, globs.images)
    ],
    dest: path.join(folders.dist, folders.images)
};

let html = {
    src: [
        path.join(folders.app, globs.html)
    ],
    dest: folders.dist
};

let copy = {
    src: [
        path.join(folders.app, globs.any),

        // ignore stuff handled by the other tasks
        utils.exclude(path.join(folders.app, globs.html)),
        utils.exclude(path.join(folders.app, globs.styles.css)),
        utils.exclude(path.join(folders.app, globs.styles.sass)),
        utils.exclude(path.join(folders.app, globs.scripts.javascript)),
        utils.exclude(path.join(folders.app, globs.scripts.typescript))
    ],
    dest: folders.dist
};

let autoprefixerBrowsers = [
    "ie >= 10",
    "ie_mob >= 10",
    "ff >= 30",
    "chrome >= 34",
    "safari >= 7",
    "opera >= 23",
    "ios >= 7",
    "android >= 4.4",
    "bb >= 10"
];

let minifyCss = { // https://www.npmjs.com/package/gulp-minify
    keepBreaks: false, // no problem here
    keepSpecialComments: true, // necessary for licensing
    compatibility: false, // no problem here
    aggressiveMerging: false // necessary because it breaks PureCSS
};

export default {
    extensions,
    folders,
    globs,
    files,
    javascript,
    typescript,
    styles,
    images,
    html,
    copy,
    autoprefixerBrowsers,
    minifyCss,
    webServerFolders,
    webServerNames
};
<|endoftext|>


<commit_before>Tests.prototype.NetworkTests = function() {
	module('Network (navigator.network)');
	test("should exist", function() {
  		expect(1);
  		ok(navigator.network != null, "navigator.network should not be null.");
	});
	test("should contain an isReachable function", function() {
		expect(2);
		ok(typeof navigator.network.isReachable != 'undefined' && navigator.network.isReachable != null, "navigator.network.isReachable should not be null.");
		ok(typeof navigator.network.isReachable == 'function', "navigator.network.isReachable should be a function.");
	});
	test("should define constants for network status", function() {
		expect(4);
		ok(NetworkStatus != null, "NetworkStatus object exists in global scope.");
		equals(NetworkStatus.NOT_REACHABLE, 0, "NetworkStatus.NOT_REACHABLE is equal to 0.");
		equals(NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK, 1, "NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK is equal to 1.");
		equals(NetworkStatus.REACHABLE_VIA_WIFI_NETWORK, 2, "NetworkStatus.REACHABLE_VIA_WIFI_NETWORK is equal to 2.");
	});
	test("isReachable function should return an object with a 'code' member as a NetworkStatus constant in its success callback", function() {
		expect(1);
		QUnit.stop(Tests.TEST_TIMEOUT);
		var hostname = "http://www.google.com";
		var win = function(p) {
			ok(p.code == NetworkStatus.NOT_REACHABLE || p.code == NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK || p.code == NetworkStatus.REACHABLE_VIA_WIFI_NETWORK, "Success callback in isReachable returns a proper object with a 'code' member equal to a NetworkStatus constant.");
			start();
		};
		navigator.network.isReachable(hostname, win);
	});
};
<commit_msg>Change test that asserts isReachable returns NetworkStatus.code to success callback.<commit_after>Tests.prototype.NetworkTests = function() {
	module('Network (navigator.network)');
	test("should exist", function() {
  		expect(1);
  		ok(navigator.network != null, "navigator.network should not be null.");
	});
	test("should contain an isReachable function", function() {
		expect(2);
		ok(typeof navigator.network.isReachable != 'undefined' && navigator.network.isReachable != null, "navigator.network.isReachable should not be null.");
		ok(typeof navigator.network.isReachable == 'function', "navigator.network.isReachable should be a function.");
	});
	test("should define constants for network status", function() {
		expect(4);
		ok(NetworkStatus != null, "NetworkStatus object exists in global scope.");
		equals(NetworkStatus.NOT_REACHABLE, 0, "NetworkStatus.NOT_REACHABLE is equal to 0.");
		equals(NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK, 1, "NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK is equal to 1.");
		equals(NetworkStatus.REACHABLE_VIA_WIFI_NETWORK, 2, "NetworkStatus.REACHABLE_VIA_WIFI_NETWORK is equal to 2.");
	});
	test("isReachable function should return one of the defined NetworkStatus constants to its success callback", function() {
		expect(1);
		QUnit.stop(Tests.TEST_TIMEOUT);
		var hostname = "http://www.google.com";
		var win = function(code) {
			ok(code == NetworkStatus.NOT_REACHABLE || code == NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK || code == NetworkStatus.REACHABLE_VIA_WIFI_NETWORK, "Success callback in isReachable returns one of the defined NetworkStatus constants.");
			start();
		};
		navigator.network.isReachable(hostname, win);
	});
};<|endoftext|>


<commit_before>var Gearman = require("../lib/gearman"),
    fs = require("fs"),
    zlib = require("zlib");

var gearman = new Gearman(); // defaults to localhost

var filepath = __dirname+"/../../wordlist.txt";

gearman.registerWorker("stream", function(payload, worker){
    // pipe stream to gzip and then to worker
    console.log("start transfer gzipped content");
    fs.createReadStream(filepath).pipe(zlib.createGzip()).pipe(worker);
});

gearman.on("connect", function(){
    console.log("Listening for jobs");
})<commit_msg>updated example<commit_after>var Gearman = require("../lib/gearman"),
    fs = require("fs"),
    zlib = require("zlib");

var gearman = new Gearman(), // defaults to localhost
    filepath = __dirname+"/../../wordlist.txt";

gearman.registerWorker("stream", function(payload, worker){
    // pipe stream to gzip and then to worker
    console.log("start transfer gzipped content");
    fs.createReadStream(filepath).pipe(zlib.createGzip()).pipe(worker);
});

gearman.on("connect", function(){
    console.log("Listening for jobs");
});<|endoftext|>


<commit_before>var _ = require('lodash')
var EventEmitter = require('events')
  .EventEmitter
/**
 * - Available Events:
 *   - tick
 *   - timer_started
 *   - timer_stopped
 *   - depths_tills_calculated
 *   - depth_asks_set
 *   - depth_bids_set
 *   - my_orders_set
 *   - public_trades_set
 *   - setMyTrades
 */
function pair (currency1, currency2, currency1Decimal, currency2Decimal) {
  EventEmitter.call(this)
  this.currency1 = currency1.toLowerCase()
  this.currency2 = currency2.toLowerCase()
  this.currency1Decimal = currency1Decimal === null || currency1Decimal === undefined ? 8 : currency1Decimal
  this.currency2Decimal = currency2Decimal === null || currency2Decimal === undefined ? 8 : currency2Decimal
  this.buyFee = 0
  this.sellFee = 0
  this.settings = {
    enabled: true,
    buyEnabled: false,
    sellEnabled: false,
    ticks: 15, // seconds
    depthsBuyMinimumAmount: 0,
    depthsSellMinimumAmount: 0,
    profitEnabled: true,
    profitTimeout: 60, // minutes
    minimumProfit: 0,
    trustBuyMaxAmount: 0,
    trustSellMaxAmount: 0,
    dangerZoneEnabled: true,
    dangerZoneType: 0,
    dangerZonePercentage: 20,
    dontBuyIfSelling: false
  }
  //
  // use setDepthAsks to set!
  //
  this.depthAsks = []
  //
  // use setDepthBids to set!
  //
  this.depthBids = []
  //
  // use setPublicTrades to set!
  //
  this.publicTrades = []
  //
  // use setMyTrades to set!
  //
  this.myTrades = []
  //
  // use setMyOrders to set!
  //
  this.myOrders = []
  this.ticker = {
    high: 0,
    low: 0,
    average: 0
  }
  this.minimumAmountToBuy = 1 / Math.pow(10, this.currency2Decimal)
  this._interval = null // private
  this.startTimer = function () {
    var self = this
    if (!self.settings.enabled) {
      return false
    }
    self.emit('timer_started')
    self.stopTimer()
    self._interval = setTimeout(function (pair) {
      pair.emit('tick', pair)
      pair.startTimer()
    }, self.settings.ticks * 1000, self)
    return true
  }
  this.stopTimer = function () {
    this.emit('timer_stopped')
    clearTimeout(this._interval)
  }
  this.setDepthAsks = function (array) {
    this.depthAsks = array
    this.emit('depth_asks_set')
  }
  this.setDepthBids = function (array) {
    this.depthBids = array
    this.emit('depth_bids_set')
  }
  this.setMyOrders = function (array) {
    this.myOrders = array
    this.emit('my_orders_set')
  }
  this.setPublicTrades = function (array) {
    this.publicTrades = array
    this.emit('public_trades_set')
  }
  this.setMyTrades = function (array) {
    this.myTrades = array
    this.emit('my_trades_set')
  }
  this.calculateDepthTills = function () {
    var self = this
    var total
    total = 0
    _.each(self.depthAsks, function (value, key) {
      self.depthAsks[key].till = total
      total += value.amount
    })
    total = 0
    _.each(self.depthBids, function (value, key) {
      self.depthBids[key].till = total
      total += value.amount
    })
    self.emit('depths_tills_calculated')
  }
}
pair.prototype.__proto__ = EventEmitter.prototype
module.exports = pair
<commit_msg>event name changed<commit_after>var _ = require('lodash')
var EventEmitter = require('events')
  .EventEmitter
/**
 * - Available Events:
 *   - tick
 *   - timer_started
 *   - timer_stopped
 *   - depths_tills_calculated
 *   - depth_asks_set
 *   - depth_bids_set
 *   - my_orders_set
 *   - public_trades_set
 *   - my_trades_set
 */
function pair (currency1, currency2, currency1Decimal, currency2Decimal) {
  EventEmitter.call(this)
  this.currency1 = currency1.toLowerCase()
  this.currency2 = currency2.toLowerCase()
  this.currency1Decimal = currency1Decimal === null || currency1Decimal === undefined ? 8 : currency1Decimal
  this.currency2Decimal = currency2Decimal === null || currency2Decimal === undefined ? 8 : currency2Decimal
  this.buyFee = 0
  this.sellFee = 0
  this.settings = {
    enabled: true,
    buyEnabled: false,
    sellEnabled: false,
    ticks: 15, // seconds
    depthsBuyMinimumAmount: 0,
    depthsSellMinimumAmount: 0,
    profitEnabled: true,
    profitTimeout: 60, // minutes
    minimumProfit: 0,
    trustBuyMaxAmount: 0,
    trustSellMaxAmount: 0,
    dangerZoneEnabled: true,
    dangerZoneType: 0,
    dangerZonePercentage: 20,
    dontBuyIfSelling: false
  }
  //
  // use setDepthAsks to set!
  //
  this.depthAsks = []
  //
  // use setDepthBids to set!
  //
  this.depthBids = []
  //
  // use setPublicTrades to set!
  //
  this.publicTrades = []
  //
  // use setMyTrades to set!
  //
  this.myTrades = []
  //
  // use setMyOrders to set!
  //
  this.myOrders = []
  this.ticker = {
    high: 0,
    low: 0,
    average: 0
  }
  this.minimumAmountToBuy = 1 / Math.pow(10, this.currency2Decimal)
  this._interval = null // private
  this.startTimer = function () {
    var self = this
    if (!self.settings.enabled) {
      return false
    }
    self.emit('timer_started')
    self.stopTimer()
    self._interval = setTimeout(function (pair) {
      pair.emit('tick', pair)
      pair.startTimer()
    }, self.settings.ticks * 1000, self)
    return true
  }
  this.stopTimer = function () {
    this.emit('timer_stopped')
    clearTimeout(this._interval)
  }
  this.setDepthAsks = function (array) {
    this.depthAsks = array
    this.emit('depth_asks_set')
  }
  this.setDepthBids = function (array) {
    this.depthBids = array
    this.emit('depth_bids_set')
  }
  this.setMyOrders = function (array) {
    this.myOrders = array
    this.emit('my_orders_set')
  }
  this.setPublicTrades = function (array) {
    this.publicTrades = array
    this.emit('public_trades_set')
  }
  this.setMyTrades = function (array) {
    this.myTrades = array
    this.emit('my_trades_set')
  }
  this.calculateDepthTills = function () {
    var self = this
    var total
    total = 0
    _.each(self.depthAsks, function (value, key) {
      self.depthAsks[key].till = total
      total += value.amount
    })
    total = 0
    _.each(self.depthBids, function (value, key) {
      self.depthBids[key].till = total
      total += value.amount
    })
    self.emit('depths_tills_calculated')
  }
}
pair.prototype.__proto__ = EventEmitter.prototype
module.exports = pair
<|endoftext|>


<commit_before>import {div, span, button, input} from '@cycle/dom';
import xs from 'xstream';
import Collection from '../../src/collection';

function todoView (complete, text) {
  return (
    div('.todo', [
      input('.complete', {attrs: {type: 'checkbox', checked: complete}}),
      span('.text', {style: {'text-decoration': complete ? 'line-through' : 'initial'}}, text),
      button('.remove', 'Remove')
    ])
  );
}

function Todo ({DOM, text, removeComplete$, filter$}) {
  const removeClick$ = DOM
    .select('.remove')
    .events('click');

  const complete$ = DOM
    .select('.complete')
    .events('change')
    .map(event => event.target.checked)
    .startWith(false);

  const removeIfComplete$ = complete$
    .map(complete => removeComplete$.filter(() => complete))
    .flatten();

  function viewUnlessFiltered ([complete, filter]) {
    if (filter(complete)) {
      return todoView(complete, text);
    } else {
      return div('.todo', {style: {display: 'none'}});
    }
  }

  return {
    DOM: xs.combine(complete$, filter$).map(viewUnlessFiltered),

    remove$: xs.merge(removeClick$, removeIfComplete$),

    complete$
  };
}

function view ([todoVtrees, todosComplete]) {
  const todosCount = todosComplete.length;
  const completeCount = todosComplete.filter(complete => complete).length;

  return (
    div('.todo-list', [
      input('.new-todo-text'),
      button('.add-todo', 'Add todo'),
      button('.remove-complete', 'Remove complete'),
      button('.show-all', 'Show all'),
      button('.show-completed', 'Show completed'),
      button('.show-active', 'Show active'),
      `${completeCount}/${todosCount} complete`,
      div('.todos', todoVtrees)
    ])
  );
}

export default function TodoList ({DOM}) {
  const removeComplete$ = DOM
    .select('.remove-complete')
    .events('click');

  const filter$ = xs.merge(
    DOM.select('.show-all').events('click').mapTo((completed) => true),
    DOM.select('.show-completed').events('click').mapTo((completed) => completed),
    DOM.select('.show-active').events('click').mapTo((completed) => !completed)
  ).startWith((completed) => true);

  const addTodoClick$ = DOM
    .select('.add-todo')
    .events('click');

  const newTodoText$ = DOM
    .select('.new-todo-text')
    .events('change')
    .map(event => event.target.value)
    .startWith('');

  const addTodo$ = newTodoText$
    .map(text => addTodoClick$.mapTo({text}))
    .flatten();

  const todos$ = Collection(Todo, {DOM, removeComplete$, filter$}, addTodo$, item => item.remove$);

  const todoVtrees$ = Collection.pluck(todos$, item => item.DOM).debug();
  const todosComplete$ = Collection.pluck(todos$, item => item.complete$);

  return {
    DOM: xs.combine(todoVtrees$, todosComplete$).map(view)
  };
}
<commit_msg>Remove errant .debug() call in todolist example<commit_after>import {div, span, button, input} from '@cycle/dom';
import xs from 'xstream';
import Collection from '../../src/collection';

function todoView (complete, text) {
  return (
    div('.todo', [
      input('.complete', {attrs: {type: 'checkbox', checked: complete}}),
      span('.text', {style: {'text-decoration': complete ? 'line-through' : 'initial'}}, text),
      button('.remove', 'Remove')
    ])
  );
}

function Todo ({DOM, text, removeComplete$, filter$}) {
  const removeClick$ = DOM
    .select('.remove')
    .events('click');

  const complete$ = DOM
    .select('.complete')
    .events('change')
    .map(event => event.target.checked)
    .startWith(false);

  const removeIfComplete$ = complete$
    .map(complete => removeComplete$.filter(() => complete))
    .flatten();

  function viewUnlessFiltered ([complete, filter]) {
    if (filter(complete)) {
      return todoView(complete, text);
    } else {
      return div('.todo', {style: {display: 'none'}});
    }
  }

  return {
    DOM: xs.combine(complete$, filter$).map(viewUnlessFiltered),

    remove$: xs.merge(removeClick$, removeIfComplete$),

    complete$
  };
}

function view ([todoVtrees, todosComplete]) {
  const todosCount = todosComplete.length;
  const completeCount = todosComplete.filter(complete => complete).length;

  return (
    div('.todo-list', [
      input('.new-todo-text'),
      button('.add-todo', 'Add todo'),
      button('.remove-complete', 'Remove complete'),
      button('.show-all', 'Show all'),
      button('.show-completed', 'Show completed'),
      button('.show-active', 'Show active'),
      `${completeCount}/${todosCount} complete`,
      div('.todos', todoVtrees)
    ])
  );
}

export default function TodoList ({DOM}) {
  const removeComplete$ = DOM
    .select('.remove-complete')
    .events('click');

  const filter$ = xs.merge(
    DOM.select('.show-all').events('click').mapTo((completed) => true),
    DOM.select('.show-completed').events('click').mapTo((completed) => completed),
    DOM.select('.show-active').events('click').mapTo((completed) => !completed)
  ).startWith((completed) => true);

  const addTodoClick$ = DOM
    .select('.add-todo')
    .events('click');

  const newTodoText$ = DOM
    .select('.new-todo-text')
    .events('change')
    .map(event => event.target.value)
    .startWith('');

  const addTodo$ = newTodoText$
    .map(text => addTodoClick$.mapTo({text}))
    .flatten();

  const todos$ = Collection(Todo, {DOM, removeComplete$, filter$}, addTodo$, item => item.remove$);

  const todoVtrees$ = Collection.pluck(todos$, item => item.DOM);
  const todosComplete$ = Collection.pluck(todos$, item => item.complete$);

  return {
    DOM: xs.combine(todoVtrees$, todosComplete$).map(view)
  };
}
<|endoftext|>


<commit_before>const _ = require('underscore')
const os = require('os');
const fs = require('fs');
const path = require('path')
const mkdirp = require('mkdirp');
const detectThread = require('./detect-thread');
const extractFiles = require('./extract-files');
const extractContacts = require('./extract-contacts');
const MessageFactory = require('../shared/message-factory')
const LocalDatabaseConnector = require('../shared/local-database-connector');

const MAX_QUEUE_LENGTH = 500
const PROCESSING_DELAY = 0

class MessageProcessor {

  constructor() {
    // The queue is a chain of Promises
    this._queue = Promise.resolve()
    this._queueLength = 0
  }

  queueLength() {
    return this._queueLength
  }

  queueIsFull() {
    return this._queueLength >= MAX_QUEUE_LENGTH
  }

  /**
   * @returns Promise that resolves when message has been processed
   * This promise will never reject, given that this function is meant to be
   * called as a fire and forget operation
   * If message processing fails, we will register the failure in the folder
   * syncState
   */
  queueMessageForProcessing({accountId, folderId, imapMessage, struct, desiredParts}) {
    return new Promise((resolve) => {
      this._queueLength++
      this._queue = this._queue.then(async () => {
        await this._processMessage({accountId, folderId, imapMessage, struct, desiredParts})
        this._queueLength--

        // To save memory, we reset the Promise chain if the queue reaches a
        // length of 0, otherwise we will continue referencing the entire chain
        // of promises that came before
        if (this._queueLength === 0) {
          this._queue = Promise.resolve()
        }
        resolve()

        // Throttle message processing to meter cpu usage
        await new Promise(r => setTimeout(r, PROCESSING_DELAY))
      })
    })
  }

  async _processMessage({accountId, folderId, imapMessage, struct, desiredParts}) {
    const db = await LocalDatabaseConnector.forAccount(accountId);
    const {Message, Folder} = db
    const folder = await Folder.findById(folderId)
    try {
      const messageValues = await MessageFactory.parseFromImap(imapMessage, desiredParts, {
        db,
        folder,
        accountId,
      });
      const existingMessage = await Message.find({where: {id: messageValues.id}});
      let processedMessage;
      if (existingMessage) {
        // TODO: optimize to not do a full message parse for existing messages
        processedMessage = await this._processExistingMessage(existingMessage, messageValues, struct)
      } else {
        processedMessage = await this._processNewMessage(messageValues, struct)
      }

      // Inflate the serialized oldestProcessedDate value, if it exists
      let oldestProcessedDate;
      if (folder.syncState && folder.syncState.oldestProcessedDate) {
        oldestProcessedDate = new Date(folder.syncState.oldestProcessedDate);
      }
      const justProcessedDate = messageValues.date ? new Date(messageValues.date) : new Date()

      // Update the oldestProcessedDate if:
      //   a) justProcessedDate is after the year 1980. We don't want to base this
      //      off of messages with borked 1970 dates.
      // AND
      //   b) i) We haven't set oldestProcessedDate yet
      //     OR
      //      ii) justProcessedDate is before oldestProcessedDate and in a different
      //          month. (We only use this to update the sync status in Nylas Mail,
      //          which uses month precision. Updating a folder's syncState triggers
      //          many re-renders in Nylas Mail, so we only do it as necessary.)
      if (justProcessedDate > new Date("1980") && (
            !oldestProcessedDate || (
              (justProcessedDate.getMonth() !== oldestProcessedDate.getMonth() ||
                justProcessedDate.getFullYear() !== oldestProcessedDate.getFullYear()) &&
              justProcessedDate < oldestProcessedDate))) {
        await folder.updateSyncState({oldestProcessedDate: justProcessedDate})
      }

      console.log(`🔃 ✉ "${messageValues.subject}" - ${messageValues.date}`)
      return processedMessage
    } catch (err) {
      console.error(`FetchMessagesInFolder: Could not build message`, {
        err,
        imapMessage,
        desiredParts,
      })

      // Keep track of uids we failed to fetch
      const {failedUIDs = []} = folder.syncState
      const {uid} = imapMessage.attributes
      if (uid) {
        await folder.updateSyncState({failedUIDs: _.uniq(failedUIDs.concat([uid]))})
      }

      // Save parse errors for future debugging
      const outJSON = JSON.stringify({imapMessage, desiredParts, result: {}});
      const outDir = path.join(os.tmpdir(), "k2-parse-errors", folder.name)
      const outFile = path.join(outDir, imapMessage.attributes.uid.toString());
      mkdirp.sync(outDir);
      fs.writeFileSync(outFile, outJSON);
      return null
    }
  }

  // Replaces ["<rfc2822messageid>", ...] with [[object Reference], ...]
  // Creates references that do not yet exist, and adds the correct
  // associations as well
  async _addReferences(db, message, thread, references) {
    const {Reference} = db;

    let existingReferences = [];
    if (references.length > 0) {
      existingReferences = await Reference.findAll({
        where: {
          rfc2822MessageId: references,
        },
      });
    }

    const refByMessageId = {};
    for (const ref of existingReferences) {
      refByMessageId[ref.rfc2822MessageId] = ref;
    }
    for (const mid of references) {
      if (!refByMessageId[mid]) {
        refByMessageId[mid] = await Reference.create({rfc2822MessageId: mid, threadId: thread.id});
      }
    }

    const referencesInstances = references.map(mid => refByMessageId[mid]);
    message.addReferences(referencesInstances);
    message.referencesOrder = referencesInstances.map(ref => ref.id);
    thread.addReferences(referencesInstances);
  }

  async _processNewMessage(messageValues, struct) {
    const {accountId} = messageValues;
    const db = await LocalDatabaseConnector.forAccount(accountId);
    const {Message} = db

    const existingMessage = await Message.findById(messageValues.id)
    if (existingMessage) {
      // This is an extremely rare case when 2 or more /new/ messages with
      // the exact same headers were queued for creation (same subject,
      // participants, timestamp, and message-id header). In this case, we
      // will ignore it and report the error
      console.warn('MessageProcessor: Encountered 2 new messages with the same id', messageValues)
      return null
    }
    const thread = await detectThread({db, messageValues});
    messageValues.threadId = thread.id;
    const createdMessage = await Message.create(messageValues);

    if (messageValues.labels) {
      await createdMessage.addLabels(messageValues.labels)
      // Note that the labels aren't officially associated until save() is called later
    }

    await this._addReferences(db, createdMessage, thread, messageValues.references);

    // TODO: need to delete dangling references somewhere (maybe at the
    // end of the sync loop?)

    const files = await extractFiles({db, messageValues, struct});
    if (files.length > 0 && !thread.hasAttachments) {
      thread.hasAttachments = true;
      await thread.save();
    }
    await extractContacts({db, messageValues});

    createdMessage.isProcessed = true;
    await createdMessage.save()
    return createdMessage
  }

  /**
   * When we send a message we store an incomplete copy in the local
   * database while we wait for the sync loop to discover the actually
   * delivered one. We store this to keep track of our delivered state and
   * to ensure it's in the sent folder.
   *
   * We also get already processed messages because they may have had their
   * folders or labels changed or had some other property updated with them.
   *
   * It'll have the basic ID, but no thread, labels, etc.
   */
  async _processExistingMessage(existingMessage, parsedMessage, struct) {
    const {accountId} = parsedMessage;
    const db = await LocalDatabaseConnector.forAccount(accountId);
    await existingMessage.update(parsedMessage);
    if (parsedMessage.labels && parsedMessage.labels.length > 0) {
      await existingMessage.setLabels(parsedMessage.labels)
    }

    let thread = await existingMessage.getThread();
    if (!existingMessage.isProcessed) {
      if (!thread) {
        thread = await detectThread({db, messageValues: parsedMessage});
        existingMessage.threadId = thread.id;
      }
      await this._addReferences(db, existingMessage, thread, parsedMessage.references);
      const files = await extractFiles({db, messageValues: existingMessage, struct});
      if (files.length > 0 && !thread.hasAttachments) {
        thread.hasAttachments = true;
        await thread.save();
      }
      await extractContacts({db, messageValues: existingMessage});
      existingMessage.isProcessed = true;
    } else {
      if (!thread) {
        throw new Error(`Existing processed message ${existingMessage.id} doesn't have thread`)
      }
    }

    await existingMessage.save();
    await thread.updateLabelsAndFolders();
    return existingMessage
  }
}

module.exports = new MessageProcessor()
<commit_msg>[local-sync] Update comment on _processExistingMessage<commit_after>const _ = require('underscore')
const os = require('os');
const fs = require('fs');
const path = require('path')
const mkdirp = require('mkdirp');
const detectThread = require('./detect-thread');
const extractFiles = require('./extract-files');
const extractContacts = require('./extract-contacts');
const MessageFactory = require('../shared/message-factory')
const LocalDatabaseConnector = require('../shared/local-database-connector');

const MAX_QUEUE_LENGTH = 500
const PROCESSING_DELAY = 0

class MessageProcessor {

  constructor() {
    // The queue is a chain of Promises
    this._queue = Promise.resolve()
    this._queueLength = 0
  }

  queueLength() {
    return this._queueLength
  }

  queueIsFull() {
    return this._queueLength >= MAX_QUEUE_LENGTH
  }

  /**
   * @returns Promise that resolves when message has been processed
   * This promise will never reject, given that this function is meant to be
   * called as a fire and forget operation
   * If message processing fails, we will register the failure in the folder
   * syncState
   */
  queueMessageForProcessing({accountId, folderId, imapMessage, struct, desiredParts}) {
    return new Promise((resolve) => {
      this._queueLength++
      this._queue = this._queue.then(async () => {
        await this._processMessage({accountId, folderId, imapMessage, struct, desiredParts})
        this._queueLength--

        // To save memory, we reset the Promise chain if the queue reaches a
        // length of 0, otherwise we will continue referencing the entire chain
        // of promises that came before
        if (this._queueLength === 0) {
          this._queue = Promise.resolve()
        }
        resolve()

        // Throttle message processing to meter cpu usage
        await new Promise(r => setTimeout(r, PROCESSING_DELAY))
      })
    })
  }

  async _processMessage({accountId, folderId, imapMessage, struct, desiredParts}) {
    const db = await LocalDatabaseConnector.forAccount(accountId);
    const {Message, Folder} = db
    const folder = await Folder.findById(folderId)
    try {
      const messageValues = await MessageFactory.parseFromImap(imapMessage, desiredParts, {
        db,
        folder,
        accountId,
      });
      const existingMessage = await Message.find({where: {id: messageValues.id}});
      let processedMessage;
      if (existingMessage) {
        // TODO: optimize to not do a full message parse for existing messages
        processedMessage = await this._processExistingMessage(existingMessage, messageValues, struct)
      } else {
        processedMessage = await this._processNewMessage(messageValues, struct)
      }

      // Inflate the serialized oldestProcessedDate value, if it exists
      let oldestProcessedDate;
      if (folder.syncState && folder.syncState.oldestProcessedDate) {
        oldestProcessedDate = new Date(folder.syncState.oldestProcessedDate);
      }
      const justProcessedDate = messageValues.date ? new Date(messageValues.date) : new Date()

      // Update the oldestProcessedDate if:
      //   a) justProcessedDate is after the year 1980. We don't want to base this
      //      off of messages with borked 1970 dates.
      // AND
      //   b) i) We haven't set oldestProcessedDate yet
      //     OR
      //      ii) justProcessedDate is before oldestProcessedDate and in a different
      //          month. (We only use this to update the sync status in Nylas Mail,
      //          which uses month precision. Updating a folder's syncState triggers
      //          many re-renders in Nylas Mail, so we only do it as necessary.)
      if (justProcessedDate > new Date("1980") && (
            !oldestProcessedDate || (
              (justProcessedDate.getMonth() !== oldestProcessedDate.getMonth() ||
                justProcessedDate.getFullYear() !== oldestProcessedDate.getFullYear()) &&
              justProcessedDate < oldestProcessedDate))) {
        await folder.updateSyncState({oldestProcessedDate: justProcessedDate})
      }

      console.log(`🔃 ✉ "${messageValues.subject}" - ${messageValues.date}`)
      return processedMessage
    } catch (err) {
      console.error(`FetchMessagesInFolder: Could not build message`, {
        err,
        imapMessage,
        desiredParts,
      })

      // Keep track of uids we failed to fetch
      const {failedUIDs = []} = folder.syncState
      const {uid} = imapMessage.attributes
      if (uid) {
        await folder.updateSyncState({failedUIDs: _.uniq(failedUIDs.concat([uid]))})
      }

      // Save parse errors for future debugging
      const outJSON = JSON.stringify({imapMessage, desiredParts, result: {}});
      const outDir = path.join(os.tmpdir(), "k2-parse-errors", folder.name)
      const outFile = path.join(outDir, imapMessage.attributes.uid.toString());
      mkdirp.sync(outDir);
      fs.writeFileSync(outFile, outJSON);
      return null
    }
  }

  // Replaces ["<rfc2822messageid>", ...] with [[object Reference], ...]
  // Creates references that do not yet exist, and adds the correct
  // associations as well
  async _addReferences(db, message, thread, references) {
    const {Reference} = db;

    let existingReferences = [];
    if (references.length > 0) {
      existingReferences = await Reference.findAll({
        where: {
          rfc2822MessageId: references,
        },
      });
    }

    const refByMessageId = {};
    for (const ref of existingReferences) {
      refByMessageId[ref.rfc2822MessageId] = ref;
    }
    for (const mid of references) {
      if (!refByMessageId[mid]) {
        refByMessageId[mid] = await Reference.create({rfc2822MessageId: mid, threadId: thread.id});
      }
    }

    const referencesInstances = references.map(mid => refByMessageId[mid]);
    message.addReferences(referencesInstances);
    message.referencesOrder = referencesInstances.map(ref => ref.id);
    thread.addReferences(referencesInstances);
  }

  async _processNewMessage(messageValues, struct) {
    const {accountId} = messageValues;
    const db = await LocalDatabaseConnector.forAccount(accountId);
    const {Message} = db

    const existingMessage = await Message.findById(messageValues.id)
    if (existingMessage) {
      // This is an extremely rare case when 2 or more /new/ messages with
      // the exact same headers were queued for creation (same subject,
      // participants, timestamp, and message-id header). In this case, we
      // will ignore it and report the error
      console.warn('MessageProcessor: Encountered 2 new messages with the same id', messageValues)
      return null
    }
    const thread = await detectThread({db, messageValues});
    messageValues.threadId = thread.id;
    const createdMessage = await Message.create(messageValues);

    if (messageValues.labels) {
      await createdMessage.addLabels(messageValues.labels)
      // Note that the labels aren't officially associated until save() is called later
    }

    await this._addReferences(db, createdMessage, thread, messageValues.references);

    // TODO: need to delete dangling references somewhere (maybe at the
    // end of the sync loop?)

    const files = await extractFiles({db, messageValues, struct});
    if (files.length > 0 && !thread.hasAttachments) {
      thread.hasAttachments = true;
      await thread.save();
    }
    await extractContacts({db, messageValues});

    createdMessage.isProcessed = true;
    await createdMessage.save()
    return createdMessage
  }

  /**
   * When we send a message we store an incomplete copy in the local
   * database while we wait for the sync loop to discover the actually
   * delivered one. We store this to keep track of our delivered state and
   * to ensure it's in the sent folder.
   *
   * It'll have the basic ID, but no thread, labels, etc.
   *
   * We also get already processed messages because they may have had their
   * folders or labels changed or had some other property updated with them,
   * or because we interrupted the sync loop before the message was fully
   * processed.
   */
  async _processExistingMessage(existingMessage, parsedMessage, struct) {
    const {accountId} = parsedMessage;
    const db = await LocalDatabaseConnector.forAccount(accountId);
    await existingMessage.update(parsedMessage);
    if (parsedMessage.labels && parsedMessage.labels.length > 0) {
      await existingMessage.setLabels(parsedMessage.labels)
    }

    let thread = await existingMessage.getThread();
    if (!existingMessage.isProcessed) {
      if (!thread) {
        thread = await detectThread({db, messageValues: parsedMessage});
        existingMessage.threadId = thread.id;
      }
      await this._addReferences(db, existingMessage, thread, parsedMessage.references);
      const files = await extractFiles({db, messageValues: existingMessage, struct});
      if (files.length > 0 && !thread.hasAttachments) {
        thread.hasAttachments = true;
        await thread.save();
      }
      await extractContacts({db, messageValues: existingMessage});
      existingMessage.isProcessed = true;
    } else {
      if (!thread) {
        throw new Error(`Existing processed message ${existingMessage.id} doesn't have thread`)
      }
    }

    await existingMessage.save();
    await thread.updateLabelsAndFolders();
    return existingMessage
  }
}

module.exports = new MessageProcessor()
<|endoftext|>


<commit_before>import { applyMiddleware, compose, createStore } from 'redux'
import thunk from 'redux-thunk'

export default ({ middlewares, custom_enhancers }, cookies) => {

  // ======================================================
  // Middleware Configuration
  // ======================================================
  const middleware = [
    thunk.withExtraArgument({
      cookies,
    }),
  ]

  // Check if the logger middleware is enabled in the config
  if (middlewares.logger && middlewares.logger.enabled) {
    const loggerOptions = middlewares.logger.options || {}
    const createLogger = require('redux-logger').createLogger
    const logger = createLogger(loggerOptions)

    middleware.push(logger)
  }

  // ======================================================
  // Add all custom middlewares
  // ======================================================
  // Add from the folder if enabled
  if (middlewares.byFolder) {
    const customMiddlewares = require('store/middleware').default()

    customMiddlewares.forEach(customMiddleware => {
      middleware.push(customMiddleware)
    })
  }

  // Add from the collection if there are any
  if (middlewares.collection && middlewares.collection.length > 0) {
    middlewares.collection.forEach(customMiddleware => {
      middleware.push(customMiddleware)
    })
  }

  // ======================================================
  // Store Enhancers
  // ======================================================
  const enhancers = []
  if (__DEBUG__ && typeof window !== 'undefined' && window) {
    const devToolsExtension = window.devToolsExtension
    if (typeof devToolsExtension === 'function') {
      enhancers.push(devToolsExtension())
    }
  }

  // Add the custom enhancers
  if (custom_enhancers.length > 0) {
    custom_enhancers.forEach(enhancer => {
      enhancers.push(enhancer)
    })
  }

  // ======================================================
  // Store Instantiation and HMR Setup
  // ======================================================
  const reducers = require('store/reducers').default
  const store = createStore(
    reducers(),
    (typeof window !== 'undefined' ? window.___INITIAL_STATE__ : {}),
    compose(
      applyMiddleware(...middleware),
      ...enhancers,
    ),
  )

  store.asyncReducers = {}
  if (module.hot) {
    module.hot.accept('store/reducers', () => {
      const reducers = require('store/reducers').default
      store.replaceReducer(reducers(store.asyncReducers))
    })
  }

  return store
}
<commit_msg>feat(esc): Give cookies with loading the middlewares so it can be used there to<commit_after>import { applyMiddleware, compose, createStore } from 'redux'
import thunk from 'redux-thunk'

export default ({ middlewares, custom_enhancers }, cookies) => {

  // ======================================================
  // Middleware Configuration
  // ======================================================
  const middleware = [
    thunk.withExtraArgument({
      cookies,
    }),
  ]

  // Check if the logger middleware is enabled in the config
  if (middlewares.logger && middlewares.logger.enabled) {
    const loggerOptions = middlewares.logger.options || {}
    const createLogger = require('redux-logger').createLogger
    const logger = createLogger(loggerOptions)

    middleware.push(logger)
  }

  // ======================================================
  // Add all custom middlewares
  // ======================================================
  // Add from the folder if enabled
  if (middlewares.byFolder) {
    // Also give cookies with the function so middelwares can also use cookiesun
    const customMiddlewares = require('store/middleware').default({
      cookies
    })

    customMiddlewares.forEach(customMiddleware => {
      middleware.push(customMiddleware)
    })
  }

  // Add from the collection if there are any
  if (middlewares.collection && middlewares.collection.length > 0) {
    middlewares.collection.forEach(customMiddleware => {
      middleware.push(customMiddleware)
    })
  }

  // ======================================================
  // Store Enhancers
  // ======================================================
  const enhancers = []
  if (__DEBUG__ && typeof window !== 'undefined' && window) {
    const devToolsExtension = window.devToolsExtension
    if (typeof devToolsExtension === 'function') {
      enhancers.push(devToolsExtension())
    }
  }

  // Add the custom enhancers
  if (custom_enhancers.length > 0) {
    custom_enhancers.forEach(enhancer => {
      enhancers.push(enhancer)
    })
  }

  // ======================================================
  // Store Instantiation and HMR Setup
  // ======================================================
  const reducers = require('store/reducers').default
  const store = createStore(
    reducers(),
    (typeof window !== 'undefined' ? window.___INITIAL_STATE__ : {}),
    compose(
      applyMiddleware(...middleware),
      ...enhancers,
    ),
  )

  store.asyncReducers = {}
  if (module.hot) {
    module.hot.accept('store/reducers', () => {
      const reducers = require('store/reducers').default
      store.replaceReducer(reducers(store.asyncReducers))
    })
  }

  return store
}
<|endoftext|>