<commit_before>'use strict';

//追加モジュールの宣言
import fs from 'fs'
import electron from 'electron'
import storage from 'electron-json-storage'

import YoutubeClient from './utilities/YoutubeClient'

let {dialog} = require('electron');

// アプリケーションをコントロールするモジュール
let app = electron.app;

// ウィンドウを作成するモジュール
let BrowserWindow = electron.BrowserWindow;

//Node.js側とHTML側で通信をするモジュール
let ipcMain = electron.ipcMain;

// スコープとアクセストークン関係
let SCOPES = [
    'https://www.googleapis.com/auth/youtube',
    'https://www.googleapis.com/auth/youtube.force-ssl',
    'https://www.googleapis.com/auth/youtube.readonly',
    'https://www.googleapis.com/auth/youtubepartner'
];

// API KEY
let APIKEY_PATH = __dirname + '/.credentials/apikey';

// OAuth 2.0 クライアント ID
let CREDENTIALS_PATH = __dirname + '/.credentials/client_secret.json';

let apikey = fs.readFileSync(APIKEY_PATH, "utf-8");

// プレイヤーウィンドウ
let playerWindow;

// 認証用ウィンドウ
let authWindow;

// コントローラーウィンドウ
let controllerWindow;

// youtube apiクライアント
let youtubeClient;

// プレイヤーウィンドウ表示処理
let createPlayerWindow = (callback) => {
    if (playerWindow != null) {
        callback();
        return;
    }
    let {width, height} = electron.screen.getPrimaryDisplay().workAreaSize
    let windowWidth = 420;
    let windowHeight = 320;

    playerWindow = new BrowserWindow({
        width: windowWidth,
        height: windowHeight,
        x: width - windowWidth,
        y: height - windowHeight,
        frame: false
    });
    playerWindow.loadURL('file://' + __dirname + '/player.html');
    playerWindow.on('closed', () => {
        playerWindow = null;
    });
    playerWindow.webContents.on("dom-ready", () => {
        callback();
    });
    playerWindow.setAlwaysOnTop(true);
};

// 認証用ウィンドウ表示処理
let createAuthWindow = (callback) => {

    // 認証用URL取得
    let authUrl = youtubeClient.getAuthUrl(SCOPES);

    authWindow = new BrowserWindow({width: 800, height: 600});
    authWindow.loadURL(authUrl);
    authWindow.openDevTools();
    authWindow.on('closed', () => {
        authWindow = null;
    });
    // 認証ウィンドウの読み込み完了後の処理
    authWindow.webContents.on("dom-ready", (e) => {
        callback();
    });
};

// コントローラーウィンドウ表示処理
let createControllerWindow = (callback) => {
    if (controllerWindow != null) {
        return;
    }
    controllerWindow = new BrowserWindow({width: 800, height: 600});
    controllerWindow.loadURL('file://' + __dirname + '/controller.html');
    controllerWindow.openDevTools();
    controllerWindow.on('closed', () => {
        app.quit();
    });
    controllerWindow.webContents.on("dom-ready", () => {
        callback();
    });
};

// 保存したtoken情報を取得
let getSavedToken = () => {
    return new Promise((resolve, reject) => {
        storage.get('token', function(error, data) {
            if (error) {
                reject(error);
                return;
            }
            resolve(data);
        });
    });
};

// 保存したtokenのチェック
let isEmptySavedToken = (token) => {
    if (
        !token ||
        Object.keys(token).length === 0 ||
        !token.access_token ||
        !token.refresh_token
    ) {
        return true;
    }
    return false;
};

// トークンを保存する
let storeToken = (token) => {
    return new Promise((resolve, reject) => {
        storage.set('token', token, function(error) {
            if (error) {
                reject(error);
                return;
            }
            resolve();
        });
    });
};

// 保存したtoken情報を削除
let removeSavedToken = () => {
    return new Promise((resolve, reject) => {
        storage.remove('token', function(error, data) {
            if (error) {
                reject(error);
                return;
            }
            resolve();
        });
    });
};

// アプリケーション資格ファイルチェック
let getCredentials = () => {
    return new Promise((resolve, reject) => {
        fs.readFile(CREDENTIALS_PATH, 'utf-8', (err, text) => {

            if (err) {
                reject(err);
                return;
            }

            let credentials = JSON.parse(text);

            if (!credentials.installed ||
                !credentials.installed.client_id ||
                !credentials.installed.client_secret ||
                !credentials.installed.redirect_uris[0])
            {
                reject("[no credential file]");
                return;
            }

            resolve({
                "client_id": credentials.installed.client_id,
                "client_secret": credentials.installed.client_secret,
                "redirect_uri": credentials.installed.redirect_uris[0]
            });
        });
    });
};

// トークンの有効期限延長処理
let refreshToken = async() => {
    if(!youtubeClient.isTokenExpired()) {
        let token = await youtubeClient.refreshToken();
        youtubeClient.setToken(token);
        storeToken(token);
   }
};

// 起動時に呼ばれるイベント
app.on('ready', async () => {

    try {

        let credentials = await getCredentials();

        youtubeClient = new YoutubeClient(credentials);

        let token = await getSavedToken();

        // 認証済トークンを認証用オブジェクトにセット
        if (!isEmptySavedToken(token)) {
            youtubeClient.setToken(token);
        }
        createControllerWindow(() => {});
    } catch(error) {
        console.log( error );
        dialog.showErrorBox("application error", "Please notify the kuniiskywalker@gmail.com");
        app.quit();
    }
});

// ウィンドウを閉じた時のイベント
app.on('window-all-closed', () => {
    if (process.platform != 'darwin') {
        app.quit();
    }
});

app.on('activate', () => {
    if (playerWindow != null) {
    }
});

// 非同期プロセス通信

// 再生プレイヤーを表示
ipcMain.on('show-player', async(event, ...args) => {
    if (playerWindow != null) {
        playerWindow.show();
        event.sender.send('show-player');
    }
});

// 再生プレイヤーを非表示
ipcMain.on('hide-player', async(event, ...args) => {
    if (playerWindow != null) {
        playerWindow.hide();
        event.sender.send('hide-player');
    }
});

// 認証ページを開く
ipcMain.on('open-auth-page', (event, ...args) => {
    createAuthWindow(() => {});
});

// ログアウト
ipcMain.on('logout', async(event, ...args) => {
    try {

        await refreshToken();

        let token = await getSavedToken();

        // ログアウト処理
        await youtubeClient.logout(token.access_token);

        // 保存済みのトークンを削除
        await removeSavedToken();

        event.sender.send('authorization', false);

    } catch (error) {
        console.log(error);
    }
});

// 自分のチャンネル一覧を取得
ipcMain.on('fetch-subscriptions', async(event, ...args) => {
    try {
        await refreshToken();
        let subscriptions = await youtubeClient.fetchSubscriptions(apikey);
        event.sender.send('fetch-subscriptions', subscriptions);
    } catch (error) {
        console.log(error);
    }
});

// チャンネルを検索
ipcMain.on('search-channel', async(event, q) => {
    try {
        await refreshToken();
        let channels = await youtubeClient.searchChannel(apikey, q);
        event.sender.send('search-channel', channels);
    } catch (error) {
        console.log(error);
    }
});

// 動画を検索
ipcMain.on('search-video', async(event, q) => {
    try {
        await refreshToken();
        let videos = await youtubeClient.searchVideo(apikey, q);
        event.sender.send('search-video', videos);
    } catch (error) {
        console.log(error);
    }
});

// プレイリストを検索
ipcMain.on('search-playlist', async(event, q) => {
    try {
        await refreshToken();
        let playlist = await youtubeClient.searchPlaylist(apikey, q);
        event.sender.send('search-playlist', playlist);
    } catch (error) {
        console.log(error);
    }
});

// 再生する動画を選択
ipcMain.on('select-video', (event, video) => {
    createPlayerWindow(() => {
        playerWindow.send('play-video', video);
    });
});

// 認証トークンをセット
ipcMain.on('set-token', async(event, code) => {
    try {

        // 入力コードからトークンを取得
        let token = await youtubeClient.getTokenByCode(code);

        // トークンを認証オブジェクトにセット
        youtubeClient.setToken(token);

        // トークンを保存
        await storeToken(token);

        event.sender.send('authorization', true);
    } catch (error) {
        console.log(error);
    }
});

// 認証チェック
ipcMain.on('check-authorization', (event) => {
    if (youtubeClient.isTokenExpired()) {
        event.sender.send('authorization', true);
    } else {
        event.sender.send('authorization', false);
    }
});
<commit_msg>change 再代入不可の変数にはconst宣言をするようにした<commit_after>'use strict';

//追加モジュールの宣言
import fs from 'fs'
import electron from 'electron'
import storage from 'electron-json-storage'

import YoutubeClient from './utilities/YoutubeClient'

const {dialog} = require('electron');

// アプリケーションをコントロールするモジュール
const app = electron.app;

// ウィンドウを作成するモジュール
const BrowserWindow = electron.BrowserWindow;

//Node.js側とHTML側で通信をするモジュール
const ipcMain = electron.ipcMain;

// スコープとアクセストークン関係
const SCOPES = [
    'https://www.googleapis.com/auth/youtube',
    'https://www.googleapis.com/auth/youtube.force-ssl',
    'https://www.googleapis.com/auth/youtube.readonly',
    'https://www.googleapis.com/auth/youtubepartner'
];

// API KEY
const APIKEY_PATH = __dirname + '/.credentials/apikey';

// OAuth 2.0 クライアント ID
const CREDENTIALS_PATH = __dirname + '/.credentials/client_secret.json';

const apikey = fs.readFileSync(APIKEY_PATH, "utf-8");

// プレイヤーウィンドウ
let playerWindow;

// 認証用ウィンドウ
let authWindow;

// コントローラーウィンドウ
let controllerWindow;

// youtube apiクライアント
let youtubeClient;

// プレイヤーウィンドウ表示処理
const createPlayerWindow = (callback) => {
    if (playerWindow != null) {
        callback();
        return;
    }
    const {width, height} = electron.screen.getPrimaryDisplay().workAreaSize
    const windowWidth = 420;
    const windowHeight = 320;

    playerWindow = new BrowserWindow({
        width: windowWidth,
        height: windowHeight,
        x: width - windowWidth,
        y: height - windowHeight,
        frame: false
    });
    playerWindow.loadURL('file://' + __dirname + '/player.html');
    playerWindow.on('closed', () => {
        playerWindow = null;
    });
    playerWindow.webContents.on("dom-ready", () => {
        callback();
    });
    playerWindow.setAlwaysOnTop(true);
};

// 認証用ウィンドウ表示処理
const createAuthWindow = (callback) => {

    // 認証用URL取得
    const authUrl = youtubeClient.getAuthUrl(SCOPES);

    authWindow = new BrowserWindow({width: 800, height: 600});
    authWindow.loadURL(authUrl);
    authWindow.openDevTools();
    authWindow.on('closed', () => {
        authWindow = null;
    });
    // 認証ウィンドウの読み込み完了後の処理
    authWindow.webContents.on("dom-ready", (e) => {
        callback();
    });
};

// コントローラーウィンドウ表示処理
const createControllerWindow = (callback) => {
    if (controllerWindow != null) {
        return;
    }
    controllerWindow = new BrowserWindow({width: 800, height: 600});
    controllerWindow.loadURL('file://' + __dirname + '/controller.html');
    controllerWindow.openDevTools();
    controllerWindow.on('closed', () => {
        app.quit();
    });
    controllerWindow.webContents.on("dom-ready", () => {
        callback();
    });
};

// 保存したtoken情報を取得
const getSavedToken = () => {
    return new Promise((resolve, reject) => {
        storage.get('token', function(error, data) {
            if (error) {
                reject(error);
                return;
            }
            resolve(data);
        });
    });
};

// 保存したtokenのチェック
const isEmptySavedToken = (token) => {
    if (
        !token ||
        Object.keys(token).length === 0 ||
        !token.access_token ||
        !token.refresh_token
    ) {
        return true;
    }
    return false;
};

// トークンを保存する
const storeToken = (token) => {
    return new Promise((resolve, reject) => {
        storage.set('token', token, function(error) {
            if (error) {
                reject(error);
                return;
            }
            resolve();
        });
    });
};

// 保存したtoken情報を削除
const removeSavedToken = () => {
    return new Promise((resolve, reject) => {
        storage.remove('token', function(error, data) {
            if (error) {
                reject(error);
                return;
            }
            resolve();
        });
    });
};

// アプリケーション資格ファイルチェック
const getCredentials = () => {
    return new Promise((resolve, reject) => {
        fs.readFile(CREDENTIALS_PATH, 'utf-8', (err, text) => {

            if (err) {
                reject(err);
                return;
            }

            const credentials = JSON.parse(text);

            if (!credentials.installed ||
                !credentials.installed.client_id ||
                !credentials.installed.client_secret ||
                !credentials.installed.redirect_uris[0])
            {
                reject("[no credential file]");
                return;
            }

            resolve({
                "client_id": credentials.installed.client_id,
                "client_secret": credentials.installed.client_secret,
                "redirect_uri": credentials.installed.redirect_uris[0]
            });
        });
    });
};

// トークンの有効期限延長処理
const refreshToken = async() => {
    if(!youtubeClient.isTokenExpired()) {
        const token = await youtubeClient.refreshToken();
        youtubeClient.setToken(token);
        storeToken(token);
   }
};

// 起動時に呼ばれるイベント
app.on('ready', async () => {

    try {

        const credentials = await getCredentials();

        youtubeClient = new YoutubeClient(credentials);

        const token = await getSavedToken();

        // 認証済トークンを認証用オブジェクトにセット
        if (!isEmptySavedToken(token)) {
            youtubeClient.setToken(token);
        }
        createControllerWindow(() => {});
    } catch(error) {
        console.log( error );
        dialog.showErrorBox("application error", "Please notify the kuniiskywalker@gmail.com");
        app.quit();
    }
});

// ウィンドウを閉じた時のイベント
app.on('window-all-closed', () => {
    if (process.platform != 'darwin') {
        app.quit();
    }
});

app.on('activate', () => {
    if (playerWindow != null) {
    }
});

// 非同期プロセス通信

// 再生プレイヤーを表示
ipcMain.on('show-player', async(event, ...args) => {
    if (playerWindow != null) {
        playerWindow.show();
        event.sender.send('show-player');
    }
});

// 再生プレイヤーを非表示
ipcMain.on('hide-player', async(event, ...args) => {
    if (playerWindow != null) {
        playerWindow.hide();
        event.sender.send('hide-player');
    }
});

// 認証ページを開く
ipcMain.on('open-auth-page', (event, ...args) => {
    createAuthWindow(() => {});
});

// ログアウト
ipcMain.on('logout', async(event, ...args) => {
    try {

        await refreshToken();

        const token = await getSavedToken();

        // ログアウト処理
        await youtubeClient.logout(token.access_token);

        // 保存済みのトークンを削除
        await removeSavedToken();

        event.sender.send('authorization', false);

    } catch (error) {
        console.log(error);
    }
});

// 自分のチャンネル一覧を取得
ipcMain.on('fetch-subscriptions', async(event, ...args) => {
    try {
        await refreshToken();
        const subscriptions = await youtubeClient.fetchSubscriptions(apikey);
        event.sender.send('fetch-subscriptions', subscriptions);
    } catch (error) {
        console.log(error);
    }
});

// チャンネルを検索
ipcMain.on('search-channel', async(event, q) => {
    try {
        await refreshToken();
        const channels = await youtubeClient.searchChannel(apikey, q);
        event.sender.send('search-channel', channels);
    } catch (error) {
        console.log(error);
    }
});

// 動画を検索
ipcMain.on('search-video', async(event, q) => {
    try {
        await refreshToken();
        const videos = await youtubeClient.searchVideo(apikey, q);
        event.sender.send('search-video', videos);
    } catch (error) {
        console.log(error);
    }
});

// プレイリストを検索
ipcMain.on('search-playlist', async(event, q) => {
    try {
        await refreshToken();
        const playlist = await youtubeClient.searchPlaylist(apikey, q);
        event.sender.send('search-playlist', playlist);
    } catch (error) {
        console.log(error);
    }
});

// 再生する動画を選択
ipcMain.on('select-video', (event, video) => {
    createPlayerWindow(() => {
        playerWindow.send('play-video', video);
    });
});

// 認証トークンをセット
ipcMain.on('set-token', async(event, code) => {
    try {

        // 入力コードからトークンを取得
        const token = await youtubeClient.getTokenByCode(code);

        // トークンを認証オブジェクトにセット
        youtubeClient.setToken(token);

        // トークンを保存
        await storeToken(token);

        event.sender.send('authorization', true);
    } catch (error) {
        console.log(error);
    }
});

// 認証チェック
ipcMain.on('check-authorization', (event) => {
    if (youtubeClient.isTokenExpired()) {
        event.sender.send('authorization', true);
    } else {
        event.sender.send('authorization', false);
    }
});
<|endoftext|>


<commit_before>'use strict';

var util = require('util');
var path = require('path');
var debug = require('debug')('question-store');
var Cache = require('question-cache');
var utils = require('./lib/utils');

/**
 * Answer stores, for persisting answers across sessions
 */

var globals;
var store;
var hints;

/**
 * Create an instance of `QuestionsStore` with the given `options`.
 *
 * ```js
 * var QuestionsStore = new QuestionsStore(options);
 * ```
 * @param {Object} `options` question store options
 * @api public
 */

function QuestionsStore(options) {
  Cache.call(this, options);
  this.createStores(this, this.options);
  this.listen(this);
}

/**
 * Inherit `quesstion-cache`
 */

util.inherits(QuestionsStore, Cache);

/**
 * Create stores for persisting data across sessions.
 *
 * - `globals`: Persist non-project-specific answers when `question.options.global` is true
 * - `store`: Persist project-specific answers
 * - `hints`: Persist project-specific hints. This is used to populate the `question.default` value.
 *
 * @param {Object} `options`
 * @return {Object}
 * @api public
 */

QuestionsStore.prototype.createStores = function(app, options) {
  // persist answers to questions with `{ global: true }`
  utils.sync(this, 'globals', function() {
    debug('creating globals store');

    if (typeof globals === 'undefined') {
      debug('created globals store');
      globals = options.globals || new utils.Store('globals', {
        cwd: utils.home()
      });
    }
    return globals;
  });

  // persist project-specific answers
  utils.sync(this, 'store', function() {
    debug('creating project store');

    if (typeof store === 'undefined') {
      store = options.store || new utils.Store(app.project);
      debug('created project store');
    }
    return store;
  });

  // persist project-specific hints
  utils.sync(this, 'hints', function() {
    debug('creating hints store');

    if (typeof hints === 'undefined') {
      var name = path.basename(app.options.cwd || process.cwd());
      hints = app.store.create(name + '/hints');
      debug('created hints store');
    }
    return hints;
  });
};

QuestionsStore.prototype.listen = function(app) {
  this.on('ask', function(val, key, question, answers) {
    if (!utils.isAnswer(val) && app.options.enabled('global')) {
      question.answer = app.globals.get(key);
      debug('no answer found, using global: "%s"', val);
    }
  });

  this.on('answer', function(val, key, question, answers) {
    var options = question._options;

    // persist to 'project' store if 'save' is not disabled
    if (!options.disabled('save')) {
      debug('saving answer in project store: %j', val);
      app.store.set(key, val);
    }

    // persist to 'global-globals' store if 'global' is enabled
    if (options.enabled('global')) {
      debug('saving answer in global store: %j', val);
      app.globals.set(key, val);
    }

    // persist to project-specific 'hint' store, if 'hint' is not disabled
    if (!options.disabled('hint')) {
      debug('saving answer in hints store: %j', val);
      app.hints.set(key, val);
    }
  });
};

/**
 * Expose `QuestionsStore`
 */

module.exports = QuestionsStore;
<commit_msg>fix typo<commit_after>'use strict';

var util = require('util');
var path = require('path');
var debug = require('debug')('question-store');
var Cache = require('question-cache');
var utils = require('./lib/utils');

/**
 * Answer stores, for persisting answers across sessions
 */

var globals;
var store;
var hints;

/**
 * Create an instance of `QuestionsStore` with the given `options`.
 *
 * ```js
 * var QuestionsStore = new QuestionsStore(options);
 * ```
 * @param {Object} `options` question store options
 * @api public
 */

function QuestionsStore(options) {
  Cache.call(this, options);
  this.createStores(this, this.options);
  this.listen(this);
}

/**
 * Inherit `quesstion-cache`
 */

util.inherits(QuestionsStore, Cache);

/**
 * Create stores for persisting data across sessions.
 *
 * - `globals`: Persist non-project-specific answers when `question.options.global` is true
 * - `store`: Persist project-specific answers
 * - `hints`: Persist project-specific hints. This is used to populate the `question.default` value.
 *
 * @param {Object} `options`
 * @return {Object}
 * @api public
 */

QuestionsStore.prototype.createStores = function(app, options) {
  // persist answers to questions with `{ global: true }`
  utils.sync(this, 'globals', function() {
    debug('creating globals store');

    if (typeof globals === 'undefined') {
      debug('created globals store');
      globals = options.globals || new utils.Store('globals', {
        cwd: utils.home()
      });
    }
    return globals;
  });

  // persist project-specific answers
  utils.sync(this, 'store', function() {
    debug('creating project store');

    if (typeof store === 'undefined') {
      store = options.store || new utils.Store(app.project);
      debug('created project store');
    }
    return store;
  });

  // persist project-specific hints
  utils.sync(this, 'hints', function() {
    debug('creating hints store');

    if (typeof hints === 'undefined') {
      var name = path.basename(app.options.cwd || process.cwd());
      hints = app.store.create(name + '/hints');
      debug('created hints store');
    }
    return hints;
  });
};

QuestionsStore.prototype.listen = function(app) {
  this.on('ask', function(val, key, question, answers) {
    if (!utils.isAnswer(val) && app.enabled('global')) {
      question.answer = app.globals.get(key);
      debug('no answer found, using global: "%s"', val);
    }
  });

  this.on('answer', function(val, key, question, answers) {
    var options = question._options;

    // persist to 'project' store if 'save' is not disabled
    if (!options.disabled('save')) {
      debug('saving answer in project store: %j', val);
      app.store.set(key, val);
    }

    // persist to 'global-globals' store if 'global' is enabled
    if (options.enabled('global')) {
      debug('saving answer in global store: %j', val);
      app.globals.set(key, val);
    }

    // persist to project-specific 'hint' store, if 'hint' is not disabled
    if (!options.disabled('hint')) {
      debug('saving answer in hints store: %j', val);
      app.hints.set(key, val);
    }
  });
};

/**
 * Expose `QuestionsStore`
 */

module.exports = QuestionsStore;
<|endoftext|>


<commit_before>var ok = require('assert').ok

function unlink (cartridge, prefix) {
    if (!prefix) {
        unlink(cartridge, 'cache')
        unlink(cartridge, 'magazine')
    } else {
        var prev = '_' + prefix + 'Previous'
        var next = '_' + prefix + 'Next'
        cartridge[prev][next] = cartridge[next]
        cartridge[next][prev] = cartridge[prev]
    }
}

function link (cartridge, previous, prefix) {
    var prev = '_' + prefix + 'Previous'
    var next = '_' + prefix + 'Next'
    cartridge[next] = previous[next]
    cartridge[prev] = previous
    cartridge[next][prev] = cartridge
    previous[next] = cartridge
}

function Cache (constructor) {
    var head = {}
    head._cachePrevious = head._cacheNext = head

    this._constructor = constructor
    this._cache = {}
    this._head = head
    this._nextKey = 0

    this.heft = 0
}

Cache.prototype.createMagazine = function () {
    var key = ':' + (++this._nextKey) + ':'
    return new Magazine(this, key)
}

Cache.prototype.purge = function (downTo) {
    downTo = Math.max(downTo, 0);
    var head = this._head
    var iterator = head
    while (this.heft > downTo && iterator._cachePrevious !== head) {
        var cartridge = iterator._cachePrevious
        if (!cartridge._holds) {
            this.heft -= cartridge.heft
            cartridge._magazine.heft -= cartridge.heft
            unlink(cartridge)
            delete this._cache[cartridge._compoundKey]
        } else {
            iterator = cartridge
        }
    }
}

function Magazine (cache, key) {
    var head = {}
    head._magazinePrevious = head._magazineNext = head

    this._cache = cache
    this._key = key
    this._head = head
    this.heft = 0
}

Magazine.prototype.hold = function (key, defaultValue) {
    var compoundKey = this._key + key
    var cartridge = this._cache._cache[compoundKey]
    if (!cartridge) {
        if (typeof defaultValue == 'function') {
            defaultValue = defaultValue()
        }
        cartridge = this._cache._cache[compoundKey] = new Cartridge(this, defaultValue, compoundKey)
    } else {
        unlink(cartridge)
    }
    link(cartridge, this._cache._head, 'cache')
    link(cartridge, this._head, 'magazine')
    cartridge._holds++
    return cartridge
}

Magazine.prototype.get = function (key) {
    var compoundKey = this._key + key
    var cartridge = this._cache._cache[compoundKey]
    if (!cartridge || !cartridge._holds) {
        throw new Error('attempt to get a cartridge not held')
    }
    return cartridge
}

Magazine.prototype.remove = function (key) {
    var compoundKey = this._key + key
    var cartridge = this._cache._cache[compoundKey]
    if (cartridge) {
        if (cartridge._holds) {
            throw new Error('attempt to remove held cartridge')
        }
        this._cache.heft -= cartridge.heft
        unlink(cartridge)
        delete this._cache._cache[compoundKey]
    }
}

Magazine.prototype.purge = function (downTo) {
    downTo = Math.max(downTo, 0);
    var head = this._head
    var iterator = head
    while (this.heft > downTo && iterator._magazinePrevious !== head) {
        var cartridge = iterator._magazinePrevious
        if (!cartridge._holds) {
            this.heft -= cartridge.heft
            this._cache.heft -= cartridge.heft
            unlink(cartridge)
            delete this._cache._cache[cartridge._compoundKey]
        } else {
            iterator = cartridge
        }
    }
}

function Cartridge (magazine, defaultValue, compoundKey) {
    this.value = defaultValue
    this._magazine = magazine
    this._compoundKey = compoundKey
    this.heft = 0
    this._holds = 0
}

Cartridge.prototype.adjustHeft = function (heft) {
    this.heft += heft
    this._magazine.heft += heft
    this._magazine._cache.heft += heft
}

Cartridge.prototype.release = function () {
    if (!this._holds) {
        throw new Error('attempt to release a cartridge not held')
    }
    this._holds--
}

module.exports = Cache
<commit_msg>Allow `-1` as a target for purge.<commit_after>var ok = require('assert').ok

function unlink (cartridge, prefix) {
    if (!prefix) {
        unlink(cartridge, 'cache')
        unlink(cartridge, 'magazine')
    } else {
        var prev = '_' + prefix + 'Previous'
        var next = '_' + prefix + 'Next'
        cartridge[prev][next] = cartridge[next]
        cartridge[next][prev] = cartridge[prev]
    }
}

function link (cartridge, previous, prefix) {
    var prev = '_' + prefix + 'Previous'
    var next = '_' + prefix + 'Next'
    cartridge[next] = previous[next]
    cartridge[prev] = previous
    cartridge[next][prev] = cartridge
    previous[next] = cartridge
}

function Cache (constructor) {
    var head = {}
    head._cachePrevious = head._cacheNext = head

    this._constructor = constructor
    this._cache = {}
    this._head = head
    this._nextKey = 0

    this.heft = 0
}

Cache.prototype.createMagazine = function () {
    var key = ':' + (++this._nextKey) + ':'
    return new Magazine(this, key)
}

Cache.prototype.purge = function (downTo) {
    downTo = Math.max(downTo, -1);
    var head = this._head
    var iterator = head
    while (this.heft > downTo && iterator._cachePrevious !== head) {
        var cartridge = iterator._cachePrevious
        if (!cartridge._holds) {
            this.heft -= cartridge.heft
            cartridge._magazine.heft -= cartridge.heft
            unlink(cartridge)
            delete this._cache[cartridge._compoundKey]
        } else {
            iterator = cartridge
        }
    }
}

function Magazine (cache, key) {
    var head = {}
    head._magazinePrevious = head._magazineNext = head

    this._cache = cache
    this._key = key
    this._head = head
    this.heft = 0
}

Magazine.prototype.hold = function (key, defaultValue) {
    var compoundKey = this._key + key
    var cartridge = this._cache._cache[compoundKey]
    if (!cartridge) {
        if (typeof defaultValue == 'function') {
            defaultValue = defaultValue()
        }
        cartridge = this._cache._cache[compoundKey] = new Cartridge(this, defaultValue, compoundKey)
    } else {
        unlink(cartridge)
    }
    link(cartridge, this._cache._head, 'cache')
    link(cartridge, this._head, 'magazine')
    cartridge._holds++
    return cartridge
}

Magazine.prototype.get = function (key) {
    var compoundKey = this._key + key
    var cartridge = this._cache._cache[compoundKey]
    if (!cartridge || !cartridge._holds) {
        throw new Error('attempt to get a cartridge not held')
    }
    return cartridge
}

Magazine.prototype.remove = function (key) {
    var compoundKey = this._key + key
    var cartridge = this._cache._cache[compoundKey]
    if (cartridge) {
        if (cartridge._holds) {
            throw new Error('attempt to remove held cartridge')
        }
        this._cache.heft -= cartridge.heft
        unlink(cartridge)
        delete this._cache._cache[compoundKey]
    }
}

Magazine.prototype.purge = function (downTo) {
    downTo = Math.max(downTo, -1);
    var head = this._head
    var iterator = head
    while (this.heft > downTo && iterator._magazinePrevious !== head) {
        var cartridge = iterator._magazinePrevious
        if (!cartridge._holds) {
            this.heft -= cartridge.heft
            this._cache.heft -= cartridge.heft
            unlink(cartridge)
            delete this._cache._cache[cartridge._compoundKey]
        } else {
            iterator = cartridge
        }
    }
}

function Cartridge (magazine, defaultValue, compoundKey) {
    this.value = defaultValue
    this._magazine = magazine
    this._compoundKey = compoundKey
    this.heft = 0
    this._holds = 0
}

Cartridge.prototype.adjustHeft = function (heft) {
    this.heft += heft
    this._magazine.heft += heft
    this._magazine._cache.heft += heft
}

Cartridge.prototype.release = function () {
    if (!this._holds) {
        throw new Error('attempt to release a cartridge not held')
    }
    this._holds--
}

module.exports = Cache
<|endoftext|>


<commit_before>import * as d3 from "d3";

export function graphScroll(){
  var windowHeight,
      dispatch = d3.dispatch("scroll", "active"),
      sections = d3.select('null'),
      i = NaN,
      sectionPos = [],
      n,
      graph = d3.select('null'),
      isFixed = null,
      isBelow = null,
      container = d3.select('body'),
      containerStart = 0,
      belowStart,
      eventId = Math.random()

  function reposition(){
    var i1 = 0
    sectionPos.forEach(function(d, i){
      if (d < pageYOffset - containerStart + 200) i1 = i
    })
    i1 = Math.min(n - 1, i1)
    if (i != i1){
      sections.classed('graph-scroll-active', function(d, i){ return i === i1 })

      dispatch.call('active', null, i1)

      i = i1
    }

    var isBelow1 = pageYOffset > belowStart
    if (isBelow != isBelow1){
      isBelow = isBelow1
      graph.classed('graph-scroll-below', isBelow)
    }
    var isFixed1 = !isBelow && pageYOffset > containerStart
    if (isFixed != isFixed1){
      isFixed = isFixed1
      graph.classed('graph-scroll-fixed', isFixed)
    }
  }

  function resize(){
    sectionPos = []
    var startPos
    sections.each(function(d, i){
      if (!i) startPos = this.getBoundingClientRect().top
      sectionPos.push(this.getBoundingClientRect().top -  startPos) })

    var containerBB = container.node().getBoundingClientRect()
    var graphBB = graph.node().getBoundingClientRect()

    containerStart = containerBB.top + pageYOffset
    belowStart = containerBB.bottom - graphBB.height  + pageYOffset
  }

  function keydown() {
    if (!isFixed) return
    var delta
    switch (d3.event.keyCode) {
      case 39: // right arrow
      if (d3.event.metaKey) return
      case 40: // down arrow
      case 34: // page down
      delta = d3.event.metaKey ? Infinity : 1 ;break
      case 37: // left arrow
      if (d3.event.metaKey) return
      case 38: // up arrow
      case 33: // page up
      delta = d3.event.metaKey ? -Infinity : -1 ;break
      case 32: // space
      delta = d3.event.shiftKey ? -1 : 1
      ;break
      default: return
    }

    var i1 = Math.max(0, Math.min(i + delta, n - 1))
    d3.select(document.documentElement)
        .interrupt()
      .transition()
        .duration(500)
        .tween("scroll", function() {
          var i = d3.interpolateNumber(pageYOffset, sectionPos[i1] + containerStart)
          return function(t) { scrollTo(0, i(t)) }
        })

    d3.event.preventDefault()
  }

  var rv ={}

  rv.container = function(_x){
    if (!_x) return container

    container = _x
    return rv
  }

  rv.graph = function(_x){
    if (!_x) return graph

    graph = _x
    return rv
  }

  rv.eventId = function(_x){
    if (!_x) return eventId

    eventId = _x
    return rv
  }

  rv.sections = function (_x){
    if (!_x) return sections

    sections = _x
    n = sections.size()

    d3.select(window)
        .on('scroll.gscroll'  + eventId, reposition)
        .on('resize.gscroll'  + eventId, resize)
        .on('keydown.gscroll' + eventId, keydown)

    resize()
    d3.timer(function() {
      reposition()
      return true
    })

    return rv
  }

  rv.on = function() {
    var value = dispatch.on.apply(dispatch, arguments);
    return value === dispatch ? rv : value;
  }

  return rv
}<commit_msg>expose an offset function<commit_after>import * as d3 from "d3";

export function graphScroll(){
  var windowHeight,
      dispatch = d3.dispatch("scroll", "active"),
      sections = d3.select('null'),
      i = NaN,
      sectionPos = [],
      n,
      graph = d3.select('null'),
      isFixed = null,
      isBelow = null,
      container = d3.select('body'),
      containerStart = 0,
      belowStart,
      eventId = Math.random(),
      offset = 0

  function reposition(){
    var i1 = 0
    sectionPos.forEach(function(d, i){
      if (d < pageYOffset - containerStart + offset) i1 = i
    })
    i1 = Math.min(n - 1, i1)
    if (i != i1){
      sections.classed('graph-scroll-active', function(d, i){ return i === i1 })

      dispatch.call('active', null, i1)

      i = i1
    }

    var isBelow1 = pageYOffset > belowStart
    if (isBelow != isBelow1){
      isBelow = isBelow1
      graph.classed('graph-scroll-below', isBelow)
    }
    var isFixed1 = !isBelow && pageYOffset > containerStart
    if (isFixed != isFixed1){
      isFixed = isFixed1
      graph.classed('graph-scroll-fixed', isFixed)
    }
  }

  function resize(){
    sectionPos = []
    var startPos
    sections.each(function(d, i){
      if (!i) startPos = this.getBoundingClientRect().top
      sectionPos.push(this.getBoundingClientRect().top -  startPos) })

    var containerBB = container.node().getBoundingClientRect()
    var graphBB = graph.node().getBoundingClientRect()

    containerStart = containerBB.top + pageYOffset
    belowStart = containerBB.bottom - graphBB.height  + pageYOffset
  }

  function keydown() {
    if (!isFixed) return
    var delta
    switch (d3.event.keyCode) {
      case 39: // right arrow
      if (d3.event.metaKey) return
      case 40: // down arrow
      case 34: // page down
      delta = d3.event.metaKey ? Infinity : 1 ;break
      case 37: // left arrow
      if (d3.event.metaKey) return
      case 38: // up arrow
      case 33: // page up
      delta = d3.event.metaKey ? -Infinity : -1 ;break
      case 32: // space
      delta = d3.event.shiftKey ? -1 : 1
      ;break
      default: return
    }

    var i1 = Math.max(0, Math.min(i + delta, n - 1))
    d3.select(document.documentElement)
        .interrupt()
      .transition()
        .duration(500)
        .tween("scroll", function() {
          var i = d3.interpolateNumber(pageYOffset, sectionPos[i1] + containerStart)
          return function(t) { scrollTo(0, i(t)) }
        })

    d3.event.preventDefault()
  }

  var rv ={}

  rv.container = function(_x){
    if (!_x) return container

    container = _x
    return rv
  }

  rv.graph = function(_x){
    if (!_x) return graph

    graph = _x
    return rv
  }

  rv.eventId = function(_x){
    if (!_x) return eventId

    eventId = _x
    return rv
  }

  rv.sections = function (_x){
    if (!_x) return sections

    sections = _x
    n = sections.size()

    d3.select(window)
        .on('scroll.gscroll'  + eventId, reposition)
        .on('resize.gscroll'  + eventId, resize)
        .on('keydown.gscroll' + eventId, keydown)

    resize()
    d3.timer(function() {
      reposition()
      return true
    })

    return rv
  }

  rv.on = function() {
    var value = dispatch.on.apply(dispatch, arguments);
    return value === dispatch ? rv : value;
  }

  rv.offset = function(_x) {
    if(!_x) return rv

    offset = _x
    return rv
  }

  return rv
}
<|endoftext|>


<commit_before>module.exports = Wire

var bitfield = require('bitfield')
var bncode = require('bncode')
var stream = require('stream')
var util = require('util')

var MESSAGE_PROTOCOL     = new Buffer('\u0013BitTorrent protocol')
var MESSAGE_KEEP_ALIVE   = new Buffer([0x00,0x00,0x00,0x00])
var MESSAGE_CHOKE        = new Buffer([0x00,0x00,0x00,0x01,0x00])
var MESSAGE_UNCHOKE      = new Buffer([0x00,0x00,0x00,0x01,0x01])
var MESSAGE_INTERESTED   = new Buffer([0x00,0x00,0x00,0x01,0x02])
var MESSAGE_UNINTERESTED = new Buffer([0x00,0x00,0x00,0x01,0x03])

var MESSAGE_RESERVED     = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]
var MESSAGE_PORT         = [0x00,0x00,0x00,0x03,0x09,0x00,0x00]

function pull (requests, piece, offset, length) {
  for (var i = 0; i < requests.length; i++) {
    var req = requests[i]
    if (req.piece !== piece || req.offset !== offset || req.length !== length) continue
    if (i === 0) requests.shift()
    else requests.splice(i, 1)
    return req
  }
  return null
}

function Request (piece, offset, length, callback) {
  this.piece = piece
  this.offset = offset
  this.length = length
  this.callback = callback
  this.timeout = null
}

util.inherits(Wire, stream.Duplex)

function Wire () {
  var self = this
  if (!(this instanceof Wire)) return new Wire()

  stream.Duplex.call(this)

  self.amChoking = true // are we choking the peer?
  self.amInterested = false // are we interested in the peer?

  self.peerChoking = true // is the peer choking us?
  self.peerInterested = false // is the peer interested in us?

  self.peerPieces = []
  self.peerExtensions = {}

  self.uploaded = 0
  self.downloaded = 0

  self.requests = []
  self.peerRequests = []

  self._keepAlive = null
  self._finished = false

  self.on('finish', function () {
    self._finished = true
    self.push(null) // cannot be half open
    clearInterval(self._keepAlive)
    self._parse(Number.MAX_VALUE, function () {})
    while (self.peerRequests.length)
      self.peerRequests.pop()
    while (self.requests.length)
      self._callback(self.requests.shift(), new Error('wire is closed'), null)
  })

  var ontimeout = function () {
    self._callback(self.requests.shift(), new Error('request has timed out'), null)
    self.emit('timeout')
  }

  self._timeout = 0
  self._ontimeout = ontimeout

  var onmessagelength = function (buffer) {
    var length = buffer.readUInt32BE(0)
    if (length) return self._parse(length, onmessage)
    self._parse(4, onmessagelength)
    self.emit('keep-alive')
  }

  var onmessage = function (buffer) {
    self._parse(4, onmessagelength)
    switch (buffer[0]) {
      case 0:
        return self._onchoke()
      case 1:
        return self._onunchoke()
      case 2:
        return self._oninterested()
      case 3:
        return self._onuninterested()
      case 4:
        return self._onhave(buffer.readUInt32BE(1))
      case 5:
        return self._onbitfield(buffer.slice(1))
      case 6:
        return self._onrequest(buffer.readUInt32BE(1),
            buffer.readUInt32BE(5), buffer.readUInt32BE(9))
      case 7:
        return self._onpiece(buffer.readUInt32BE(1),
            buffer.readUInt32BE(5), buffer.slice(9))
      case 8:
        return self._oncancel(buffer.readUInt32BE(1),
            buffer.readUInt32BE(5), buffer.readUInt32BE(9))
      case 9:
        return self._onport(buffer.readUInt16BE(1))
      case 20:
        return self._onextended(bncode.decode(buffer))
    }
    self.emit('unknownmessage', buffer)
  }

  self._buffer = []
  self._bufferSize = 0
  self._parser = null
  self._parserSize = 0

  self._parse(1, function (buffer) {
    var pstrlen = buffer.readUInt8(0)
    self._parse(pstrlen + 48, function (handshake) {
      var protocol = handshake.slice(0, pstrlen)
      if (protocol.toString() !== 'BitTorrent protocol') {
        self.emit('error', 'Wire not speaking BitTorrent protocol: ', protocol)
        self.destroy()
        return
      }
      handshake = handshake.slice(pstrlen)
      self._onhandshake(handshake.slice(8, 28), handshake.slice(28, 48), {
        dht: !!(handshake[7] & 0x01), // see bep_0005
        extended: !!(handshake[5] & 0x10) // see bep_0010
      })
      self._parse(4, onmessagelength)
    })
  })
}

//
// MESSAGES
//

/**
 * Message: "handshake" <pstrlen><pstr><reserved><info_hash><peer_id>
 * @param  {Buffer|string} infoHash
 * @param  {Buffer|string} peerId
 * @param  {Object} extensions
 */
Wire.prototype.handshake = function (infoHash, peerId, extensions) {
  if (typeof infoHash === 'string')
    infoHash = new Buffer(infoHash, 'hex')
  if (typeof peerId === 'string')
    peerId = new Buffer(peerId)
  if (infoHash.length !== 20 || peerId.length !== 20)
    throw new Error('infoHash and peerId MUST have length 20')

  var reserved = new Buffer(MESSAGE_RESERVED)

  // enable extended message
  reserved[5] |= 0x10

  if (extensions && extensions.dht)
    reserved[7] |= 1

  this._push(Buffer.concat([MESSAGE_PROTOCOL, reserved, infoHash, peerId]))
}

Wire.prototype.choke = function () {
  if (this.amChoking) return
  this.amChoking = true
  while (this.peerRequests.length) this.peerRequests.pop()
  this._push(MESSAGE_CHOKE)
}

Wire.prototype.unchoke = function () {
  if (!this.amChoking) return
  this.amChoking = false
  this._push(MESSAGE_UNCHOKE)
}

Wire.prototype.interested = function () {
  if (this.amInterested) return
  this.amInterested = true
  this._push(MESSAGE_INTERESTED)
}

Wire.prototype.uninterested = function () {
  if (!this.amInterested) return
  this.amInterested = false
  this._push(MESSAGE_UNINTERESTED)
}

Wire.prototype.have = function (i) {
  this._message(4, [i], null)
}

Wire.prototype.bitfield = function (bitfield) {
  if (bitfield.buffer) bitfield = bitfield.buffer // support bitfield objects
  this._message(5, [], bitfield)
}

Wire.prototype.request = function (i, offset, length, callback) {
  if (!callback) callback = function () {}
  if (this._finished)   return callback(new Error('wire is closed'))
  if (this.peerChoking) return callback(new Error('peer is choking'))
  this.requests.push(new Request(i, offset, length, callback))
  this._updateTimeout()
  this._message(6, [i, offset, length], null)
}

Wire.prototype.piece = function (i, offset, buffer) {
  this.uploaded += buffer.length
  this.emit('upload', buffer.length)
  this._message(7, [i, offset], buffer)
}

Wire.prototype.cancel = function (i, offset, length) {
  this._callback(pull(this.requests, i, offset, length), new Error('request was cancelled'), null)
  this._message(8, [i, offset, length], null)
}

/**
 * Message: "port" <len=0003><id=9><listen-port>
 * @param {Number} port
 */
Wire.prototype.port = function (port) {
  var message = new Buffer(MESSAGE_PORT)
  message.writeUInt16BE(port, 5)
  this._push(message)
}

Wire.prototype.extended = function (ext_number, msg) {
  var ext_id = new Buffer(1)
  ext_id.writeUInt8(ext_number, 0)
  this._message(20, [], Buffer.concat([ext_id, bncode.encode(msg)]))
}

Wire.prototype.setKeepAlive = function (bool) {
  clearInterval(this._keepAlive)
  if (bool === false) return
  this._keepAlive = setInterval(this._push.bind(this, MESSAGE_KEEP_ALIVE), 60000)
}

Wire.prototype.setTimeout = function (ms, fn) {
  if (this.requests.length) clearTimeout(this.requests[0].timeout)
  this._timeout = ms
  this._updateTimeout()
  if (fn) this.on('timeout', fn)
}

Wire.prototype.destroy = function () {
  this.emit('close')
  this.end()
}

// inbound

Wire.prototype._onhandshake = function (infoHash, peerId, extensions) {
  this.peerExtensions = extensions
  this.emit('handshake', infoHash, peerId, extensions)
}

Wire.prototype._oninterested = function () {
  this.peerInterested = true
  this.emit('interested')
}

Wire.prototype._onuninterested = function () {
  this.peerInterested = false
  this.emit('uninterested')
}

Wire.prototype._onchoke = function () {
  this.peerChoking = true
  this.emit('choke')
  while (this.requests.length)
    this._callback(this.requests.shift(), new Error('peer is choking'), null)
}

Wire.prototype._onunchoke = function () {
  this.peerChoking = false
  this.emit('unchoke')
}

Wire.prototype._onbitfield = function (buffer) {
  var pieces = bitfield(buffer)
  for (var i = 0; i < 8 * buffer.length; i++) {
    this.peerPieces[i] = pieces.get(i)
  }
  this.emit('bitfield', buffer)
}

Wire.prototype._onhave = function (i) {
  this.peerPieces[i] = true
  this.emit('have', i)
}

Wire.prototype._onrequest = function (i, offset, length) {
  if (this.amChoking) return

  var respond = function (err, buffer) {
    if (err || request !== pull(this.peerRequests, i, offset, length))
      return
    this.piece(i, offset, buffer)
  }.bind(this)

  var request = new Request(i, offset, length, respond)
  this.peerRequests.push(request)
  this.emit('request', i, offset, length, respond)
}

Wire.prototype._oncancel = function (i, offset, length) {
  pull(this.peerRequests, i, offset, length)
  this.emit('cancel', i, offset, length)
}

Wire.prototype._onpiece = function (i, offset, buffer) {
  this._callback(pull(this.requests, i, offset, buffer.length), null, buffer)
  this.downloaded += buffer.length
  this.emit('download', buffer.length)
  this.emit('piece', i, offset, buffer)
}

Wire.prototype._onport = function (port) {
  this.emit('port', port)
}

Wire.prototype._onextended = function (ext) {
  this.emit('extended', ext)
}

// helpers and streams

Wire.prototype._callback = function (request, err, buffer) {
  if (!request) return
  if (request.timeout)
    clearTimeout(request.timeout)
  if (!this.peerChoking && !this._finished)
    this._updateTimeout()
  request.callback(err, buffer)
}

Wire.prototype._updateTimeout = function () {
  if (!this._timeout || !this.requests.length || this.requests[0].timeout)
    return
  this.requests[0].timeout = setTimeout(this._ontimeout, this._timeout)
}

Wire.prototype._message = function (id, numbers, data) {
  var dataLength = data ? data.length : 0
  var buffer = new Buffer(5 + 4 * numbers.length)

  buffer.writeUInt32BE(buffer.length + dataLength - 4, 0)
  buffer[4] = id
  for (var i = 0; i < numbers.length; i++) {
    buffer.writeUInt32BE(numbers[i], 5 + 4 * i)
  }

  this._push(buffer)
  if (data)
    this._push(data)
}

/**
 * Push a message to the remote peer.
 * @param {Buffer} data
 */
Wire.prototype._push = function (data) {
  if (this._finished) return
  return this.push(data)
}

Wire.prototype._parse = function (size, parser) {
  this._parserSize = size
  this._parser = parser
}

/**
 * Duplex stream method. Called whenever the upstream has data for us.
 * @param  {Buffer|string} data
 * @param  {string}   encoding
 * @param  {function} cb
 */
Wire.prototype._write = function (data, encoding, callback) {
  this._bufferSize += data.length
  this._buffer.push(data)

  while (this._bufferSize >= this._parserSize) {
    var buffer = (this._buffer.length === 1)
      ? this._buffer[0]
      : Buffer.concat(this._buffer)
    this._bufferSize -= this._parserSize
    this._buffer = this._bufferSize
      ? [buffer.slice(this._parserSize)]
      : []
    this._parser(buffer.slice(0, this._parserSize))
  }

  callback(null) // Signal that we're ready for more data
}

/**
 * Duplex stream method. Called whenever the downstream wants data. No-op
 * since we'll just push data whenever we get it. Extra data will be buffered
 * in memory (we don't want to apply backpressure to peers!).
 */
Wire.prototype._read = function () {}
<commit_msg>use bitfield object directly<commit_after>module.exports = Wire

var bitfield = require('bitfield')
var bncode = require('bncode')
var stream = require('stream')
var util = require('util')

var MESSAGE_PROTOCOL     = new Buffer('\u0013BitTorrent protocol')
var MESSAGE_KEEP_ALIVE   = new Buffer([0x00,0x00,0x00,0x00])
var MESSAGE_CHOKE        = new Buffer([0x00,0x00,0x00,0x01,0x00])
var MESSAGE_UNCHOKE      = new Buffer([0x00,0x00,0x00,0x01,0x01])
var MESSAGE_INTERESTED   = new Buffer([0x00,0x00,0x00,0x01,0x02])
var MESSAGE_UNINTERESTED = new Buffer([0x00,0x00,0x00,0x01,0x03])

var MESSAGE_RESERVED     = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]
var MESSAGE_PORT         = [0x00,0x00,0x00,0x03,0x09,0x00,0x00]

function pull (requests, piece, offset, length) {
  for (var i = 0; i < requests.length; i++) {
    var req = requests[i]
    if (req.piece !== piece || req.offset !== offset || req.length !== length) continue
    if (i === 0) requests.shift()
    else requests.splice(i, 1)
    return req
  }
  return null
}

function Request (piece, offset, length, callback) {
  this.piece = piece
  this.offset = offset
  this.length = length
  this.callback = callback
  this.timeout = null
}

util.inherits(Wire, stream.Duplex)

function Wire () {
  var self = this
  if (!(this instanceof Wire)) return new Wire()

  stream.Duplex.call(this)

  self.amChoking = true // are we choking the peer?
  self.amInterested = false // are we interested in the peer?

  self.peerChoking = true // is the peer choking us?
  self.peerInterested = false // is the peer interested in us?

  self.peerPieces = []
  self.peerExtensions = {}

  self.uploaded = 0
  self.downloaded = 0

  self.requests = []
  self.peerRequests = []

  self._keepAlive = null
  self._finished = false

  self.on('finish', function () {
    self._finished = true
    self.push(null) // cannot be half open
    clearInterval(self._keepAlive)
    self._parse(Number.MAX_VALUE, function () {})
    while (self.peerRequests.length)
      self.peerRequests.pop()
    while (self.requests.length)
      self._callback(self.requests.shift(), new Error('wire is closed'), null)
  })

  var ontimeout = function () {
    self._callback(self.requests.shift(), new Error('request has timed out'), null)
    self.emit('timeout')
  }

  self._timeout = 0
  self._ontimeout = ontimeout

  var onmessagelength = function (buffer) {
    var length = buffer.readUInt32BE(0)
    if (length) return self._parse(length, onmessage)
    self._parse(4, onmessagelength)
    self.emit('keep-alive')
  }

  var onmessage = function (buffer) {
    self._parse(4, onmessagelength)
    switch (buffer[0]) {
      case 0:
        return self._onchoke()
      case 1:
        return self._onunchoke()
      case 2:
        return self._oninterested()
      case 3:
        return self._onuninterested()
      case 4:
        return self._onhave(buffer.readUInt32BE(1))
      case 5:
        return self._onbitfield(buffer.slice(1))
      case 6:
        return self._onrequest(buffer.readUInt32BE(1),
            buffer.readUInt32BE(5), buffer.readUInt32BE(9))
      case 7:
        return self._onpiece(buffer.readUInt32BE(1),
            buffer.readUInt32BE(5), buffer.slice(9))
      case 8:
        return self._oncancel(buffer.readUInt32BE(1),
            buffer.readUInt32BE(5), buffer.readUInt32BE(9))
      case 9:
        return self._onport(buffer.readUInt16BE(1))
      case 20:
        return self._onextended(bncode.decode(buffer))
    }
    self.emit('unknownmessage', buffer)
  }

  self._buffer = []
  self._bufferSize = 0
  self._parser = null
  self._parserSize = 0

  self._parse(1, function (buffer) {
    var pstrlen = buffer.readUInt8(0)
    self._parse(pstrlen + 48, function (handshake) {
      var protocol = handshake.slice(0, pstrlen)
      if (protocol.toString() !== 'BitTorrent protocol') {
        self.emit('error', 'Wire not speaking BitTorrent protocol: ', protocol)
        self.destroy()
        return
      }
      handshake = handshake.slice(pstrlen)
      self._onhandshake(handshake.slice(8, 28), handshake.slice(28, 48), {
        dht: !!(handshake[7] & 0x01), // see bep_0005
        extended: !!(handshake[5] & 0x10) // see bep_0010
      })
      self._parse(4, onmessagelength)
    })
  })
}

//
// MESSAGES
//

/**
 * Message: "handshake" <pstrlen><pstr><reserved><info_hash><peer_id>
 * @param  {Buffer|string} infoHash
 * @param  {Buffer|string} peerId
 * @param  {Object} extensions
 */
Wire.prototype.handshake = function (infoHash, peerId, extensions) {
  if (typeof infoHash === 'string')
    infoHash = new Buffer(infoHash, 'hex')
  if (typeof peerId === 'string')
    peerId = new Buffer(peerId)
  if (infoHash.length !== 20 || peerId.length !== 20)
    throw new Error('infoHash and peerId MUST have length 20')

  var reserved = new Buffer(MESSAGE_RESERVED)

  // enable extended message
  reserved[5] |= 0x10

  if (extensions && extensions.dht)
    reserved[7] |= 1

  this._push(Buffer.concat([MESSAGE_PROTOCOL, reserved, infoHash, peerId]))
}

Wire.prototype.choke = function () {
  if (this.amChoking) return
  this.amChoking = true
  while (this.peerRequests.length) this.peerRequests.pop()
  this._push(MESSAGE_CHOKE)
}

Wire.prototype.unchoke = function () {
  if (!this.amChoking) return
  this.amChoking = false
  this._push(MESSAGE_UNCHOKE)
}

Wire.prototype.interested = function () {
  if (this.amInterested) return
  this.amInterested = true
  this._push(MESSAGE_INTERESTED)
}

Wire.prototype.uninterested = function () {
  if (!this.amInterested) return
  this.amInterested = false
  this._push(MESSAGE_UNINTERESTED)
}

Wire.prototype.have = function (i) {
  this._message(4, [i], null)
}

Wire.prototype.bitfield = function (bitfield) {
  this._message(5, [], bitfield)
}

Wire.prototype.request = function (i, offset, length, callback) {
  if (!callback) callback = function () {}
  if (this._finished)   return callback(new Error('wire is closed'))
  if (this.peerChoking) return callback(new Error('peer is choking'))
  this.requests.push(new Request(i, offset, length, callback))
  this._updateTimeout()
  this._message(6, [i, offset, length], null)
}

Wire.prototype.piece = function (i, offset, buffer) {
  this.uploaded += buffer.length
  this.emit('upload', buffer.length)
  this._message(7, [i, offset], buffer)
}

Wire.prototype.cancel = function (i, offset, length) {
  this._callback(pull(this.requests, i, offset, length), new Error('request was cancelled'), null)
  this._message(8, [i, offset, length], null)
}

/**
 * Message: "port" <len=0003><id=9><listen-port>
 * @param {Number} port
 */
Wire.prototype.port = function (port) {
  var message = new Buffer(MESSAGE_PORT)
  message.writeUInt16BE(port, 5)
  this._push(message)
}

Wire.prototype.extended = function (ext_number, msg) {
  var ext_id = new Buffer(1)
  ext_id.writeUInt8(ext_number, 0)
  this._message(20, [], Buffer.concat([ext_id, bncode.encode(msg)]))
}

Wire.prototype.setKeepAlive = function (bool) {
  clearInterval(this._keepAlive)
  if (bool === false) return
  this._keepAlive = setInterval(this._push.bind(this, MESSAGE_KEEP_ALIVE), 60000)
}

Wire.prototype.setTimeout = function (ms, fn) {
  if (this.requests.length) clearTimeout(this.requests[0].timeout)
  this._timeout = ms
  this._updateTimeout()
  if (fn) this.on('timeout', fn)
}

Wire.prototype.destroy = function () {
  this.emit('close')
  this.end()
}

// inbound

Wire.prototype._onhandshake = function (infoHash, peerId, extensions) {
  this.peerExtensions = extensions
  this.emit('handshake', infoHash, peerId, extensions)
}

Wire.prototype._oninterested = function () {
  this.peerInterested = true
  this.emit('interested')
}

Wire.prototype._onuninterested = function () {
  this.peerInterested = false
  this.emit('uninterested')
}

Wire.prototype._onchoke = function () {
  this.peerChoking = true
  this.emit('choke')
  while (this.requests.length)
    this._callback(this.requests.shift(), new Error('peer is choking'), null)
}

Wire.prototype._onunchoke = function () {
  this.peerChoking = false
  this.emit('unchoke')
}

Wire.prototype._onbitfield = function (buffer) {
  var pieces = bitfield(buffer)
  for (var i = 0; i < 8 * buffer.length; i++) {
    this.peerPieces[i] = pieces.get(i)
  }
  this.emit('bitfield', buffer)
}

Wire.prototype._onhave = function (i) {
  this.peerPieces[i] = true
  this.emit('have', i)
}

Wire.prototype._onrequest = function (i, offset, length) {
  if (this.amChoking) return

  var respond = function (err, buffer) {
    if (err || request !== pull(this.peerRequests, i, offset, length))
      return
    this.piece(i, offset, buffer)
  }.bind(this)

  var request = new Request(i, offset, length, respond)
  this.peerRequests.push(request)
  this.emit('request', i, offset, length, respond)
}

Wire.prototype._oncancel = function (i, offset, length) {
  pull(this.peerRequests, i, offset, length)
  this.emit('cancel', i, offset, length)
}

Wire.prototype._onpiece = function (i, offset, buffer) {
  this._callback(pull(this.requests, i, offset, buffer.length), null, buffer)
  this.downloaded += buffer.length
  this.emit('download', buffer.length)
  this.emit('piece', i, offset, buffer)
}

Wire.prototype._onport = function (port) {
  this.emit('port', port)
}

Wire.prototype._onextended = function (ext) {
  this.emit('extended', ext)
}

// helpers and streams

Wire.prototype._callback = function (request, err, buffer) {
  if (!request) return
  if (request.timeout)
    clearTimeout(request.timeout)
  if (!this.peerChoking && !this._finished)
    this._updateTimeout()
  request.callback(err, buffer)
}

Wire.prototype._updateTimeout = function () {
  if (!this._timeout || !this.requests.length || this.requests[0].timeout)
    return
  this.requests[0].timeout = setTimeout(this._ontimeout, this._timeout)
}

Wire.prototype._message = function (id, numbers, data) {
  var dataLength = data ? data.length : 0
  var buffer = new Buffer(5 + 4 * numbers.length)

  buffer.writeUInt32BE(buffer.length + dataLength - 4, 0)
  buffer[4] = id
  for (var i = 0; i < numbers.length; i++) {
    buffer.writeUInt32BE(numbers[i], 5 + 4 * i)
  }

  this._push(buffer)
  if (data)
    this._push(data)
}

/**
 * Push a message to the remote peer.
 * @param {Buffer} data
 */
Wire.prototype._push = function (data) {
  if (this._finished) return
  return this.push(data)
}

Wire.prototype._parse = function (size, parser) {
  this._parserSize = size
  this._parser = parser
}

/**
 * Duplex stream method. Called whenever the upstream has data for us.
 * @param  {Buffer|string} data
 * @param  {string}   encoding
 * @param  {function} cb
 */
Wire.prototype._write = function (data, encoding, callback) {
  this._bufferSize += data.length
  this._buffer.push(data)

  while (this._bufferSize >= this._parserSize) {
    var buffer = (this._buffer.length === 1)
      ? this._buffer[0]
      : Buffer.concat(this._buffer)
    this._bufferSize -= this._parserSize
    this._buffer = this._bufferSize
      ? [buffer.slice(this._parserSize)]
      : []
    this._parser(buffer.slice(0, this._parserSize))
  }

  callback(null) // Signal that we're ready for more data
}

/**
 * Duplex stream method. Called whenever the downstream wants data. No-op
 * since we'll just push data whenever we get it. Extra data will be buffered
 * in memory (we don't want to apply backpressure to peers!).
 */
Wire.prototype._read = function () {}
<|endoftext|>


<commit_before>
var chalk_global = require('chalk');

var symbol = process.platform === 'win32'
	? {tick: '√', cross: '×'}
	: {tick: '✔', cross: '✖'};

function strmul(s, n) {
	var r = '';
	for (var i = 0; i < n; ++i) {
		r += s;
	}
	return r;
}

var SummaryReporter = function(baseReporterDecorator, config) {
	baseReporterDecorator(this);

	// Configuration
	config.summaryReporter = config.summaryReporter || {};
	var show = config.summaryReporter.show || 'failed';
	var specLength = config.summaryReporter.specLength || 50;
	var overviewColumn = config.summaryReporter.overviewColumn === false ? false : true;

	// We use our own instance, respecting config.color
	var chalk = new chalk_global.constructor({enabled: config.colors});

	var specorder, specresults;

	this.specSuccess = this.specFailure = this.specSkipped = function(browser, result) {
		if (!result.suite) {
			return;
		}

		var specid = result.suite.join('/') + '/' + result.description;
		if (!(specid in specresults)) {
			specorder.push(specid);
			specresults[specid] = {
				spec: result.suite.slice().concat(result.description),
				results: Object.create(null)
			};
		}
		specresults[specid].results[browser.id] = result;
	}

	// Previously printed spec path
	var currentPath;
	this.printSpecLabel = function(path) {
		var indent = "  ";
		path.forEach(function(s, i) {
			// We keep the current comon prefix and start to print
			// the new information on the first difference.
			if (i < currentPath.length && s != currentPath[i]) {
				currentPath.length = i;
			}
			if (i >= currentPath.length) {
				var label = indent + s;
				if (label.length > specLength) {
					label = label.slice(0, specLength-3) + '...';
				}
				this.writeCommonMsg(label);

				if (i < path.length-1) {
					this.writeCommonMsg("\n");
				}
				else {
					this.writeCommonMsg(strmul(' ', specLength - label.length));
				}
				currentPath.push(s);
			}
			indent += "  ";
		}, this);
	};

	this.printResultLabel = function(result) {
		if (result === undefined)
			this.writeCommonMsg(chalk.yellow(' ? '));
		else if (result.skipped)
			this.writeCommonMsg(chalk.yellow(' - '));
		else if (result.success) {
			if (!result.partial)
				this.writeCommonMsg(chalk.green(' ' +  symbol.tick + ' '));
			else
				this.writeCommonMsg(chalk.yellow('(' +  symbol.tick + ')'));
		}
		else {
			this.writeCommonMsg(chalk.red(' ' + symbol.cross + ' '));
		}
	};

	this.printTableHeader = function(browsers) {
		this.writeCommonMsg(strmul(' ', specLength));
		if (overviewColumn) {
			this.writeCommonMsg(' all  ');
		}
		browsers.forEach(function(browser, i) {
			this.writeCommonMsg(' '+  i + ' ');
		}, this);
		this.writeCommonMsg('\n');
	}

	this.onRunStart = function() {
		this._browsers = [];
		currentPath = [];
		specorder = [];
		specresults = Object.create(null);
	}

	this.onRunComplete = function(browsers, results) {
		this.writeCommonMsg(chalk.bold(chalk.underline('SUMMARY')) + '\n');

		// Browser overview
		browsers.forEach(function(browser, i) {
			this.writeCommonMsg(' ' + i + ': ' + this.renderBrowser(browser) + '\n');
		}, this);

		if (!specorder.length) {
			this.writeCommonMsg(chalk.red('No tests did run in any browsers.'));
			return;
		}

		var tableHeaderShown = false;

		// Test details
		var counts = { shown: 0, hidden: 0 };
		specorder.forEach(function(specid) {
			var sr = specresults[specid];
			// Collect information from all browsers
			var summary = { skipped_some: false, ran_some: false, success: true };
			browsers.forEach(function(b) {
				if (sr.results[b.id]) {
					if (sr.results[b.id].skipped) {
						summary.skipped_some = true;
					}
					else {
						summary.ran_some = true;
						summary.success = summary.success && sr.results[b.id].success;
					}
				}
				else {
					summary.skipped_some = true;
				}
			});

			if (summary.success) {
				// Maybe we don't even want to show it
				if (show == 'failed') {
					if (summary.ran_some)
						counts.hidden++;
					return;
				}
				if (show == 'skipped' && !summary.skipped_some) {
					if (summary.ran_some)
						counts.hidden++;
					return;
				}
			}

			// We want to actually display it
			if (!tableHeaderShown) {
				this.printTableHeader(browsers);
				tableHeaderShown = true;
			}

			this.printSpecLabel(sr.spec);
			if (overviewColumn) {
				this.writeCommonMsg(' ');
				this.printResultLabel(summary);
				this.writeCommonMsg('  ');
			}
			browsers.forEach(function(browser, i) {
				this.printResultLabel(sr.results[browser.id], i);
			}, this);
			this.writeCommonMsg("\n");
			counts.shown++;
		}, this);

		if (counts.hidden) {
			this.writeCommonMsg("  " + chalk.green(''+counts.hidden) +
				(counts.shown ? " more" : "") +
				" test cases successful in all browsers\n")
		}
	};
}

SummaryReporter.$inject = [
	'baseReporterDecorator',
	'config'
];

module.exports = {
	'reporter:summary': ['type', SummaryReporter]
};

<commit_msg>proper table header spacing for 100 browsers<commit_after>
var chalk_global = require('chalk');

var symbol = process.platform === 'win32'
	? {tick: '√', cross: '×'}
	: {tick: '✔', cross: '✖'};

function strmul(s, n) {
	var r = '';
	for (var i = 0; i < n; ++i) {
		r += s;
	}
	return r;
}

var SummaryReporter = function(baseReporterDecorator, config) {
	baseReporterDecorator(this);

	// Configuration
	config.summaryReporter = config.summaryReporter || {};
	var show = config.summaryReporter.show || 'failed';
	var specLength = config.summaryReporter.specLength || 50;
	var overviewColumn = config.summaryReporter.overviewColumn === false ? false : true;

	// We use our own instance, respecting config.color
	var chalk = new chalk_global.constructor({enabled: config.colors});

	var specorder, specresults;

	this.specSuccess = this.specFailure = this.specSkipped = function(browser, result) {
		if (!result.suite) {
			return;
		}

		var specid = result.suite.join('/') + '/' + result.description;
		if (!(specid in specresults)) {
			specorder.push(specid);
			specresults[specid] = {
				spec: result.suite.slice().concat(result.description),
				results: Object.create(null)
			};
		}
		specresults[specid].results[browser.id] = result;
	}

	// Previously printed spec path
	var currentPath;
	this.printSpecLabel = function(path) {
		var indent = "  ";
		path.forEach(function(s, i) {
			// We keep the current comon prefix and start to print
			// the new information on the first difference.
			if (i < currentPath.length && s != currentPath[i]) {
				currentPath.length = i;
			}
			if (i >= currentPath.length) {
				var label = indent + s;
				if (label.length > specLength) {
					label = label.slice(0, specLength-3) + '...';
				}
				this.writeCommonMsg(label);

				if (i < path.length-1) {
					this.writeCommonMsg("\n");
				}
				else {
					this.writeCommonMsg(strmul(' ', specLength - label.length));
				}
				currentPath.push(s);
			}
			indent += "  ";
		}, this);
	};

	this.printResultLabel = function(result) {
		if (result === undefined)
			this.writeCommonMsg(chalk.yellow(' ? '));
		else if (result.skipped)
			this.writeCommonMsg(chalk.yellow(' - '));
		else if (result.success) {
			if (!result.partial)
				this.writeCommonMsg(chalk.green(' ' +  symbol.tick + ' '));
			else
				this.writeCommonMsg(chalk.yellow('(' +  symbol.tick + ')'));
		}
		else {
			this.writeCommonMsg(chalk.red(' ' + symbol.cross + ' '));
		}
	};

	this.printTableHeader = function(browsers) {
		this.writeCommonMsg(strmul(' ', specLength));
		if (overviewColumn) {
			this.writeCommonMsg(' all  ');
		}
		browsers.forEach(function(browser, i) {
			this.writeCommonMsg((i < 10 && ' ') +  i + ' ');
		}, this);
		this.writeCommonMsg('\n');
	}

	this.onRunStart = function() {
		this._browsers = [];
		currentPath = [];
		specorder = [];
		specresults = Object.create(null);
	}

	this.onRunComplete = function(browsers, results) {
		this.writeCommonMsg(chalk.bold(chalk.underline('SUMMARY')) + '\n');

		// Browser overview
		browsers.forEach(function(browser, i) {
			this.writeCommonMsg(' ' + i + ': ' + this.renderBrowser(browser) + '\n');
		}, this);

		if (!specorder.length) {
			this.writeCommonMsg(chalk.red('No tests did run in any browsers.'));
			return;
		}

		var tableHeaderShown = false;

		// Test details
		var counts = { shown: 0, hidden: 0 };
		specorder.forEach(function(specid) {
			var sr = specresults[specid];
			// Collect information from all browsers
			var summary = { skipped_some: false, ran_some: false, success: true };
			browsers.forEach(function(b) {
				if (sr.results[b.id]) {
					if (sr.results[b.id].skipped) {
						summary.skipped_some = true;
					}
					else {
						summary.ran_some = true;
						summary.success = summary.success && sr.results[b.id].success;
					}
				}
				else {
					summary.skipped_some = true;
				}
			});

			if (summary.success) {
				// Maybe we don't even want to show it
				if (show == 'failed') {
					if (summary.ran_some)
						counts.hidden++;
					return;
				}
				if (show == 'skipped' && !summary.skipped_some) {
					if (summary.ran_some)
						counts.hidden++;
					return;
				}
			}

			// We want to actually display it
			if (!tableHeaderShown) {
				this.printTableHeader(browsers);
				tableHeaderShown = true;
			}

			this.printSpecLabel(sr.spec);
			if (overviewColumn) {
				this.writeCommonMsg(' ');
				this.printResultLabel(summary);
				this.writeCommonMsg('  ');
			}
			browsers.forEach(function(browser, i) {
				this.printResultLabel(sr.results[browser.id], i);
			}, this);
			this.writeCommonMsg("\n");
			counts.shown++;
		}, this);

		if (counts.hidden) {
			this.writeCommonMsg("  " + chalk.green(''+counts.hidden) +
				(counts.shown ? " more" : "") +
				" test cases successful in all browsers\n")
		}
	};
}

SummaryReporter.$inject = [
	'baseReporterDecorator',
	'config'
];

module.exports = {
	'reporter:summary': ['type', SummaryReporter]
};

<|endoftext|>


<commit_before>/**
 * Configure the persistence options
 */

var config = {};
if (process.env.MONGOLAB_URI) {
    var BotkitStorage = require('botkit-storage-mongo');
    config = {
        storage: BotkitStorage({mongoUri: process.env.MONGOLAB_URI}),
    };
} else {
    config = {
        json_file_store: ((process.env.TOKEN)?'./db_slack_bot_ci/':'./db_slack_bot_a/'), //use a different name if an app or CI
    };
}

/**
 * Are being run as an app or a custom integration? The initialization will differ, depending
 */

if (process.env.TOKEN || process.env.SLACK_TOKEN) {
    //Treat this as a custom integration
    var customIntegration = require('./lib/custom_integrations');
    var token = (process.env.TOKEN) ? process.env.TOKEN : process.env.SLACK_TOKEN;
    var controller = customIntegration.configure(token, config);
} else if (process.env.CLIENT_ID && process.env.CLIENT_SECRET && process.env.PORT) {
    //Treat this as an app
    var app = require('./lib/apps');
    var controller = app.configure(process.env.PORT, process.env.CLIENT_ID, process.env.CLIENT_SECRET, config, onInstallation);
} else {
    console.log('Error: If this is a custom integration, please specify TOKEN in the environment. If this is an app, please specify CLIENTID, CLIENTSECRET, and PORT in the environment');
    process.exit(1);
}

/**
 * A demonstration for how to handle websocket events. In this case, just log when we have and have not
 * been disconnected from the websocket. In the future, it would be super awesome to be able to specify
 * a reconnect policy, and do reconnections automatically. In the meantime, we aren't going to attempt reconnects,
 * WHICH IS A B0RKED WAY TO HANDLE BEING DISCONNECTED. So we need to fix this.
 *
 * TODO: fixed b0rked reconnect behavior
 */
// Handle events related to the websocket connection to Slack
controller.on('rtm_open', function (bot) {
    console.log('** The RTM api just connected!');
});

controller.on('rtm_close', function (bot) {
    console.log('** The RTM api just closed');
    // you may want to attempt to re-open
});

/**
 * Core bot logic goes here!
 */
// BEGIN EDITING HERE!
var runHappening = false;
var runList = [];

controller.on('bot_channel_join', function (bot, message) {
    bot.reply(message, "I'm here!")
});

controller.hears('hello', 'direct_message', function (bot, message) {
    bot.reply(message, 'Hello!');
});

controller.hears('start', 'direct_mention', function(bot, message) {

    if (runHappening) {
        return bot.reply(message, 'Already gathering requests for a run in' + runChannel);
    }

    bot.reply(message, 'Holy La Croix! <@' + message.username + '> is going for a Pop-a-Top run. Who  <@here> has a request? Tell me what you want and I will let them know!');
    runHappening = true;
    console.log('===runHappening = ' + runHappening);
});

controller.hears('echo', 'direct_mention', function(bot, messsage) {
    bot.reply(message, '...echo');
    console.log('========Message ' + message);
});

//When the run is ended, collect runList into a string format, return it to user that owns the run
//TODO: Add metadata to the runList array containing owner, channel, etc.
controller.hears('end', 'direct_mention', function(bot, message) {
    if (!runHappening) {
        return bot.reply(message, 'Nobody is on a run right now. Start a new run with `start`');
    }

    runHappening = false;
    console.log('===runHappening = ' + runHappening);
    str = JSON.stringify(runList[0]);
    console.log('===runList = ' + str);

    bot.reply(message, 'The run is now over!');

    bot.startConversation(message, function(err,convo){
        convo.ask('<@' + message.username + '> want to see the list?', [{
            pattern: bot.utterances.yes,
            callback: function(response, convo) {
                console.log('====runList = ');
                console.log(runList[0]);

                //Initialize empty listString array
                var listString = [];

//////////////// I'm confused here now. What I want to do is take all the entries in runList and get them ready to be spat out at the run owner in the format NAME1: request1, request2, request3 **NEWLINE** NAME2: request1, request2
//////////////// Ultimately I need one string that has the results parsed into a single string, complete with escaped linebreaks to pass to bot.say

                //Loop through runList and convert entries into strings
                for (var i = 0; i < runList.length; i++) {

                    //Create a variable that contains the object to push into
                    var x = {username: message.username, requests: runList[i].requests.join(', ')};

                    //Use .push() to add anything to an array
                  listString.push(x);
                    bot.reply(message, (listString));
                }

                console.log('===Heard yes, output runList object');
                console.log('===Output listString= ' + listString);
                convo.next();
            }
        }, {
            pattern: bot.utterances.no,
            callback: function(response,convo) {
                bot.say({channel: message.channel, text: 'Okey Dokey. That was fun, see you later!'});
                // runModel.clearStatuses();
                console.log('Heard no')
                convo.next();
            }
        }]);
    });
});

/*runList = [
  {username: 'Em', requests: ['pamp mouse']}
]
*/
controller.hears('(.*)', 'direct_mention, direct_message', function(bot, message) {
    if (runHappening) {
        gatherRequest(bot, message);
    }
});

function gatherRequest(bot, message) {
    console.log('===request = ' + message.text);
    bot.reply(message, 'Got it! Thanks ' + message.username);
    addDesire(message);
    function addDesire(message){
      for (var i = 0; i < runList.length; i++) {

    //Check if the current user already has an entry in runList under their username
      if (runList[i].username === message.username) {

        //If so, .push() the text of their message into the requests array
        runList[i].requests.push(message.text);
        console.log('===addDesire: user already exists in runList! Pushing their request into the requests array for their username');
      }

      //If not, .push() a new Object into runList with their username and request
      else {

        //Create a variable pushy to hold the object we are going to push into runList
        var pushy = {username: message.username, requests: [message.text]};
        runList.push(pushy);
      }
    }};
    console.log(pushy);
    console.log('===Pushing request to runList');

   /* runModel.addRequest({
        request: message.text,
        user: message.user
    });
    */

}
<commit_msg>logging pushy var to see if it is populated before being pushed to runList<commit_after>/**
 * Configure the persistence options
 */

var config = {};
if (process.env.MONGOLAB_URI) {
    var BotkitStorage = require('botkit-storage-mongo');
    config = {
        storage: BotkitStorage({mongoUri: process.env.MONGOLAB_URI}),
    };
} else {
    config = {
        json_file_store: ((process.env.TOKEN)?'./db_slack_bot_ci/':'./db_slack_bot_a/'), //use a different name if an app or CI
    };
}

/**
 * Are being run as an app or a custom integration? The initialization will differ, depending
 */

if (process.env.TOKEN || process.env.SLACK_TOKEN) {
    //Treat this as a custom integration
    var customIntegration = require('./lib/custom_integrations');
    var token = (process.env.TOKEN) ? process.env.TOKEN : process.env.SLACK_TOKEN;
    var controller = customIntegration.configure(token, config);
} else if (process.env.CLIENT_ID && process.env.CLIENT_SECRET && process.env.PORT) {
    //Treat this as an app
    var app = require('./lib/apps');
    var controller = app.configure(process.env.PORT, process.env.CLIENT_ID, process.env.CLIENT_SECRET, config, onInstallation);
} else {
    console.log('Error: If this is a custom integration, please specify TOKEN in the environment. If this is an app, please specify CLIENTID, CLIENTSECRET, and PORT in the environment');
    process.exit(1);
}

/**
 * A demonstration for how to handle websocket events. In this case, just log when we have and have not
 * been disconnected from the websocket. In the future, it would be super awesome to be able to specify
 * a reconnect policy, and do reconnections automatically. In the meantime, we aren't going to attempt reconnects,
 * WHICH IS A B0RKED WAY TO HANDLE BEING DISCONNECTED. So we need to fix this.
 *
 * TODO: fixed b0rked reconnect behavior
 */
// Handle events related to the websocket connection to Slack
controller.on('rtm_open', function (bot) {
    console.log('** The RTM api just connected!');
});

controller.on('rtm_close', function (bot) {
    console.log('** The RTM api just closed');
    // you may want to attempt to re-open
});

/**
 * Core bot logic goes here!
 */
// BEGIN EDITING HERE!
var runHappening = false;
var runList = [];

controller.on('bot_channel_join', function (bot, message) {
    bot.reply(message, "I'm here!")
});

controller.hears('hello', 'direct_message', function (bot, message) {
    bot.reply(message, 'Hello!');
});

controller.hears('start', 'direct_mention', function(bot, message) {

    if (runHappening) {
        return bot.reply(message, 'Already gathering requests for a run in' + runChannel);
    }

    bot.reply(message, 'Holy La Croix! <@' + message.username + '> is going for a Pop-a-Top run. Who  <@here> has a request? Tell me what you want and I will let them know!');
    runHappening = true;
    console.log('===runHappening = ' + runHappening);
});

controller.hears('echo', 'direct_mention', function(bot, messsage) {
    bot.reply(message, '...echo');
    console.log('========Message ' + message);
});

//When the run is ended, collect runList into a string format, return it to user that owns the run
//TODO: Add metadata to the runList array containing owner, channel, etc.
controller.hears('end', 'direct_mention', function(bot, message) {
    if (!runHappening) {
        return bot.reply(message, 'Nobody is on a run right now. Start a new run with `start`');
    }

    runHappening = false;
    console.log('===runHappening = ' + runHappening);
    str = JSON.stringify(runList[0]);
    console.log('===runList = ' + str);

    bot.reply(message, 'The run is now over!');

    bot.startConversation(message, function(err,convo){
        convo.ask('<@' + message.username + '> want to see the list?', [{
            pattern: bot.utterances.yes,
            callback: function(response, convo) {
                console.log('====runList = ');
                console.log(runList[0]);

                //Initialize empty listString array
                var listString = [];

//////////////// I'm confused here now. What I want to do is take all the entries in runList and get them ready to be spat out at the run owner in the format NAME1: request1, request2, request3 **NEWLINE** NAME2: request1, request2
//////////////// Ultimately I need one string that has the results parsed into a single string, complete with escaped linebreaks to pass to bot.say

                //Loop through runList and convert entries into strings
                for (var i = 0; i < runList.length; i++) {

                    //Create a variable that contains the object to push into
                    var x = {username: message.username, requests: runList[i].requests.join(', ')};

                    //Use .push() to add anything to an array
                  listString.push(x);
                    bot.reply(message, (listString));
                }

                console.log('===Heard yes, output runList object');
                console.log('===Output listString= ' + listString);
                convo.next();
            }
        }, {
            pattern: bot.utterances.no,
            callback: function(response,convo) {
                bot.say({channel: message.channel, text: 'Okey Dokey. That was fun, see you later!'});
                // runModel.clearStatuses();
                console.log('Heard no')
                convo.next();
            }
        }]);
    });
});

/*runList = [
  {username: 'Em', requests: ['pamp mouse']}
]
*/
controller.hears('(.*)', 'direct_mention, direct_message', function(bot, message) {
    if (runHappening) {
        gatherRequest(bot, message);
    }
});

function gatherRequest(bot, message) {
    console.log('===request = ' + message.text);
    bot.reply(message, 'Got it! Thanks ' + message.username);
    addDesire(message);
    function addDesire(message){
      for (var i = 0; i < runList.length; i++) {

    //Check if the current user already has an entry in runList under their username
      if (runList[i].username === message.username) {

        //If so, .push() the text of their message into the requests array
        runList[i].requests.push(message.text);
        console.log('===addDesire: user already exists in runList! Pushing their request into the requests array for their username');
      }

      //If not, .push() a new Object into runList with their username and request
      else {

        //Create a variable pushy to hold the object we are going to push into runList
        var pushy = {username: message.username, requests: [message.text]};
        console.log(pushy);
        runList.push(pushy);
      }
    }};

    console.log('===Pushing request to runList');

   /* runModel.addRequest({
        request: message.text,
        user: message.user
    });
    */

}
<|endoftext|>


<commit_before>var express = require('express');
var app = express();
var http = require('https');
var fs = require('fs');

function getUSD(callback) {
	http.get('https://blockchain.info/ticker', function(res) {
    var body = '';
    res.setEncoding('utf8');

    res.on('data', function(data) {
      body += data;
    });

    res.on('end', function() {
      body = JSON.parse(body);
      callback(body.USD.last);
    });

  }).on('error', function(e) {
    console.log('Got error: ' + e.message);
  });
}

app.get('/calculate', function (req, res) {

  // Website you wish to allow to connect
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');

  // Request methods you wish to allow
  res.setHeader('Access-Control-Allow-Methods', 'GET');

  // Request headers you wish to allow
  res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');

  // Set to true if you need the website to include cookies in the requests sent
  // to the API (e.g. in case you use sessions)
  res.setHeader('Access-Control-Allow-Credentials', true);

  getUSD(function(data){
    res.send(data.toString());
  });
});

app.get('/', function (req, res) {
  fs.readFile('HTML/index.html', function(err, text){
    res.setHeader('Content-Type', 'text/html');
    res.end(text);
  });
  return;
});

var server = app.listen(3000, function () {
	var host = server.address().address;
	var port = server.address().port;

	console.log('Example app listening at http://%s:%s', host, port);
});
<commit_msg>made it heroku compatible<commit_after>var express = require('express');
var app = express();
var http = require('https');
var fs = require('fs');

function getUSD(callback) {
	http.get('https://blockchain.info/ticker', function(res) {
    var body = '';
    res.setEncoding('utf8');

    res.on('data', function(data) {
      body += data;
    });

    res.on('end', function() {
      body = JSON.parse(body);
      callback(body.USD.last);
    });

  }).on('error', function(e) {
    console.log('Got error: ' + e.message);
  });
}

app.get('/calculate', function (req, res) {

  // Website you wish to allow to connect
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');

  // Request methods you wish to allow
  res.setHeader('Access-Control-Allow-Methods', 'GET');

  // Request headers you wish to allow
  res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');

  // Set to true if you need the website to include cookies in the requests sent
  // to the API (e.g. in case you use sessions)
  res.setHeader('Access-Control-Allow-Credentials', true);

  getUSD(function(data){
    res.send(data.toString());
  });
});

app.get('/', function (req, res) {
  fs.readFile('HTML/index.html', function(err, text){
    res.setHeader('Content-Type', 'text/html');
    res.end(text);
  });
  return;
});

var server = app.listen(process.env.PORT || 3000, function () {
	var host = server.address().address;
	var port = server.address().port;

	console.log('Example app listening at http://%s:%s', host, port);
});
<|endoftext|>