<commit_before>#!/usr/bin/env node
'use strict';
var path = require('path')
  , cp = require('child_process')
  , fs = require('fs')
  , glob = require('glob')
  , ora = require('ora')
  , pkg = require('./package')
  , spinner = ora({
    text: 'The hamsters are working...'
    , spinner: 'star'
  })

  , shouldInstall = process.argv.indexOf('--no-install') === -1 && process.argv.indexOf('-ni') === -1
  , command = shouldInstall ?
      'npm cache clear && npm install && npm prune && npm dedupe && npm shrinkwrap --dev' :
      'npm prune && npm dedupe && npm shrinkwrap --dev'

  , isProblematic = function (badDeps) {
      return function (name) {
        return badDeps.indexOf(name) !== -1;
      };
  }

  , cleanDependencies = function (depObject, testFunction) {
    return Object.keys(depObject).reduce(function (result, key) {
      if (!testFunction(key)) {
        if ( depObject[key].dependencies) {
          result[key] = depObject[key];
          result[key].dependencies = cleanDependencies(depObject[key].dependencies, testFunction);
        } else {
          result[key] = depObject[key];
        }
      }

      return result;
    }, {});
  };

if (process.argv.indexOf('-v') >= 0 || process.argv.indexOf('--version') >= 0) {
  console.log(pkg.version);
  process.exit(0);
}

if (process.argv.indexOf('-h') >= 0 || process.argv.indexOf('--help') >= 0) {
  console.log(`safe-shrinkwrap version: ${pkg.version}`);
  console.log('');
  console.log(`    -ni, --no-install : doesn't install`)
  console.log(`    -v, --version : outputs just the version`)
  console.log(`    -h, --help : outputs this help information`)
  process.exit(0);
}

if (shouldInstall) {
  console.log('Clearing NPM cache and Proceeding to reinstall before we shrinkwrap');
}

spinner.start();

cp.exec(command, function (err, stdout, stderr) {
  if (err) {
    console.log(err);
    return;
  }

  glob('./node_modules/**/package.json', function (err, files) {
    var shrinkwrapped = require(path.join(process.cwd(), './npm-shrinkwrap.json'))
      , badDeps = files.reduce(function (accum, file) {
          try {
            var depDetails = require(path.join(process.cwd(), file));

            if (typeof depDetails.os !== 'undefined') {
              accum.push(depDetails.name);
            }
          } catch (e) {
          }

          return accum;
        }, [])
      , clean = cleanDependencies(shrinkwrapped.dependencies || {}, isProblematic(badDeps))
      , finalObj = JSON.parse(JSON.stringify(shrinkwrapped));

    finalObj.dependencies = clean;

    fs.writeFile(path.join(process.cwd(), './npm-shrinkwrap.json'), JSON.stringify(finalObj));
    fs.writeFile(path.join(process.cwd(), './npm-shrinkwrap.unsafe.json'), JSON.stringify(shrinkwrapped));
    spinner.stop();

    console.log("They're done! So is your shrinkwrap file.");
  });
});
<commit_msg>don't dedupe<commit_after>#!/usr/bin/env node
'use strict';
var path = require('path')
  , cp = require('child_process')
  , fs = require('fs')
  , glob = require('glob')
  , ora = require('ora')
  , pkg = require('./package')
  , spinner = ora({
    text: 'The hamsters are working...'
    , spinner: 'star'
  })

  , shouldInstall = process.argv.indexOf('--no-install') === -1 && process.argv.indexOf('-ni') === -1
  , command = shouldInstall ?
      'npm cache clear && npm install && npm prune && npm shrinkwrap --dev' :
      'npm prune && npm dedupe && npm shrinkwrap --dev'

  , isProblematic = function (badDeps) {
      return function (name) {
        return badDeps.indexOf(name) !== -1;
      };
  }

  , cleanDependencies = function (depObject, testFunction) {
    return Object.keys(depObject).reduce(function (result, key) {
      if (!testFunction(key)) {
        if ( depObject[key].dependencies) {
          result[key] = depObject[key];
          result[key].dependencies = cleanDependencies(depObject[key].dependencies, testFunction);
        } else {
          result[key] = depObject[key];
        }
      }

      return result;
    }, {});
  };

if (process.argv.indexOf('-v') >= 0 || process.argv.indexOf('--version') >= 0) {
  console.log(pkg.version);
  process.exit(0);
}

if (process.argv.indexOf('-h') >= 0 || process.argv.indexOf('--help') >= 0) {
  console.log(`safe-shrinkwrap version: ${pkg.version}`);
  console.log('');
  console.log(`    -ni, --no-install : doesn't install`)
  console.log(`    -v, --version : outputs just the version`)
  console.log(`    -h, --help : outputs this help information`)
  process.exit(0);
}

if (shouldInstall) {
  console.log('Clearing NPM cache and Proceeding to reinstall before we shrinkwrap');
}

spinner.start();

cp.exec(command, function (err, stdout, stderr) {
  if (err) {
    console.log(err);
    return;
  }

  glob('./node_modules/**/package.json', function (err, files) {
    var shrinkwrapped = require(path.join(process.cwd(), './npm-shrinkwrap.json'))
      , badDeps = files.reduce(function (accum, file) {
          try {
            var depDetails = require(path.join(process.cwd(), file));

            if (typeof depDetails.os !== 'undefined') {
              accum.push(depDetails.name);
            }
          } catch (e) {
          }

          return accum;
        }, [])
      , clean = cleanDependencies(shrinkwrapped.dependencies || {}, isProblematic(badDeps))
      , finalObj = JSON.parse(JSON.stringify(shrinkwrapped));

    finalObj.dependencies = clean;

    fs.writeFile(path.join(process.cwd(), './npm-shrinkwrap.json'), JSON.stringify(finalObj));
    fs.writeFile(path.join(process.cwd(), './npm-shrinkwrap.unsafe.json'), JSON.stringify(shrinkwrapped));
    spinner.stop();

    console.log("They're done! So is your shrinkwrap file.");
  });
});
<|endoftext|>


<commit_before>var resemblejs = require('resemblejs-tolerance');
var canvas     = require('canvas');
var imageType  = require('image-type');

module.exports = function() {
    //override FileReader
    this.FileReader = function(){};
    this.FileReader.prototype.readAsDataURL = function(buffer) {
        this.onload({
            target: {
                result: 'data:image/' + imageType(buffer) + ';base64,' + buffer.toString('base64')
            }
        });
    };

    //override Image
    this.Image = canvas.Image;
    this.Image.prototype.setAttribute = function() {};

    //override document.createElement('canvas')
    this.document = {
        createElement: function(tag) {
            if(tag !== 'canvas') {
                return false;
            }

            return new canvas;
        }
    };

    return resemblejs.resemble.apply(this, arguments);
};
<commit_msg>Don't add extra nesting<commit_after>var resemblejs = require('resemblejs-tolerance');
var canvas     = require('canvas');
var imageType  = require('image-type');

module.exports = function() {
    //override FileReader
    this.FileReader = function(){};
    this.FileReader.prototype.readAsDataURL = function(buffer) {
        this.onload({
            target: {
                result: 'data:image/' + imageType(buffer) + ';base64,' + buffer.toString('base64')
            }
        });
    };

    //override Image
    this.Image = canvas.Image;
    this.Image.prototype.setAttribute = function() {};

    //override document.createElement('canvas')
    this.document = {
        createElement: function(tag) {
            if(tag !== 'canvas') {
                return false;
            }

            return new canvas;
        }
    };

    return resemblejs.apply(this, arguments);
};
<|endoftext|>


<commit_before><commit_msg>Emit an event for playbackRate<commit_after><|endoftext|>


<commit_before><commit_msg>cleanup, add comments<commit_after><|endoftext|>


<commit_before><commit_msg>correct default publication date. Using moment to do it in your schema config code dates posts to when the server was launched, not today.<commit_after><|endoftext|>


<commit_before>import Global from "../blocks/global"
import Util from "./util"

/*

  In order to coexist with newspring.cc while we migrate to
  Apollos driven sites, we take all unknown traffic and send
  it to newspring.cc as a just in case. This lets us share navigation,
  click links that are relative, and 404.

*/
const NotFound = [
  {
    path: "*",
    onEnter: function (nextState, replaceState) {
      console.log(this, nextState, replaceState)
      let link = `${nextState.location.pathname}${nextState.location.search}${nextState.location.hash}`

      if (Meteor.isCordova) {
        window.open(`//newspring.cc${link}`)
        return
      }

      if (Meteor.isClient) {
        // stay at current route while we wait on the browser
        let current = [...nextState.location.previous].pop()
        if (current) {
          replaceState(null, current, nextState.location.search)
        }

        // leave when the browser will let you
        window.location = `https://newspring.cc${link}`
        return
      }

      replaceState(null, `//newspring.cc${link}`)

    }
  }
]

const Routes = {
  childRoutes: [].concat(
    Util.Routes,
    NotFound
  )
}

export {

  // combined export of app routes
  Routes

}
<commit_msg>only dynamically reroute on the client<commit_after>import Global from "../blocks/global"
import Util from "./util"

/*

  In order to coexist with newspring.cc while we migrate to
  Apollos driven sites, we take all unknown traffic and send
  it to newspring.cc as a just in case. This lets us share navigation,
  click links that are relative, and 404.

*/
const NotFound = [
  {
    path: "*",
    onEnter: function (nextState, replaceState) {
      let link = `${nextState.location.pathname}${nextState.location.search}${nextState.location.hash}`

      if (Meteor.isCordova) {
        window.open(`//newspring.cc${link}`)
        return
      }

      if (Meteor.isClient) {
        // stay at current route while we wait on the browser
        let current = [...nextState.location.previous].pop()
        if (current) {
          replaceState(null, current, nextState.location.search)
        }

        // leave when the browser will let you
        window.location = `https://newspring.cc${link}`
        return
      }

      // replaceState(null, `//newspring.cc${link}`)

    }
  }
]

const Routes = {
  childRoutes: [].concat(
    Util.Routes,
    NotFound
  )
}

export {

  // combined export of app routes
  Routes

}
<|endoftext|>


<commit_before><commit_msg>Move redirect logic to functions<commit_after><|endoftext|>


<commit_before>'use strict';

module.exports = (bot, config) => {
    if (typeof config.data.permissions === 'undefined') {
        config.data.permissions = {};
    }
    const users = config.data.permissions;
    return {
        isAdmin: (user) => {
            return Boolean(users[user] && users[user].admin);
        },
        isOperator: (user, channel) => {
            return Boolean(users[user] && users[user].channels && users[user].channels.indexOf(channel) > -1);
        },
        setAdmin: (user, value) => {
            if (typeof users[user] === 'undefined') {
                users[user] = {};
            }
            users[user].admin = !!value;
            return config.save();
        },
        setOperator: (user, channel, value) => {
            if (typeof users[user] === 'undefined') {
                users[user] = {};
            }
            if (typeof users[user].channels === 'undefined') {
                users[user].channels = [];
            }
            const index = users[user].channels.indexOf(channel);
            if (value && index === -1) {
                users[user].channels.push(channel);
                return config.save();
            } else if (!value && index > -1) {
                users.splice(index, 1);
                return config.save();
            }
        }
    };
};<commit_msg>Store permissions with lowercase users & channels, and make lookups case insensitive<commit_after>'use strict';

module.exports = (bot, config) => {
    if (typeof config.data.permissions === 'undefined') {
        config.data.permissions = {};
    }
    const users = config.data.permissions;
    const getUser = (name) => {
        name = name.toLowerCase();
        let result = users[name];
        if (typeof result === 'undefined') {
            result = {admin: false, channels: []};
            users[name] = result;
        }
        return result;
    };
    const saveConfig = () => {
        for (const name in users) {
            const user = users[name];
            if (!user.admin && user.channels.length === 0) {
                delete users[name];
            }
        }
        return config.save();
    };

    for (const name in users) {
        const user = users[name];
        if (typeof user.admin !== 'boolean') {
            user.admin = false;
        }
        if (!Array.isArray(user.channels)) {
            user.channels = [];
        }
    }

    return {
        isAdmin: (name) => {
            return getUser(name).admin;
        },
        isOperator: (name, channel) => {
            return getUser(name).channels.indexOf(channel.toLowerCase()) > -1;
        },
        setAdmin: (name, value) => {
            const user = getUser(name);
            user.admin = !!value;
            return saveConfig();
        },
        setOperator: (name, channel, value) => {
            const user = getUser(name);
            const index = user.channels.indexOf(channel.toLowerCase());
            if (value && index === -1) {
                user.channels.push(channel.toLowerCase());
                return saveConfig();
            } else if (!value && index > -1) {
                user.channels.splice(index, 1);
                return saveConfig();
            }
        }
    };
};<|endoftext|>


<commit_before><commit_msg>prevent accidentl allocation<commit_after><|endoftext|>


<commit_before><commit_msg>actually start the study<commit_after><|endoftext|>


<commit_before>const _ = require('lodash');
const Promise = require('bluebird');
const errors = require('@tryghost/errors');
const logging = require('@tryghost/logging');
const tpl = require('@tryghost/tpl');

const settingsCache = require('./settings-cache');
const config = require('./config');

const messages = {
    errorMessage: 'The \\{\\{{helperName}\\}\\} helper is not available.',
    errorContext: 'The {flagName} flag must be enabled in labs if you wish to use the \\{\\{{helperName}\\}\\} helper.',
    errorHelp: 'See {url}'
};

// flags in this list always return `true`, allows quick global enable prior to full flag removal
const GA_FEATURES = [
    'customThemeSettings'
];

// NOTE: this allowlist is meant to be used to filter out any unexpected
//       input for the "labs" setting value
const BETA_FEATURES = [
    'activitypub',
    'multipleProducts'
];

const ALPHA_FEATURES = [
    'oauthLogin',
    'membersActivity',
    'cardSettingsPanel',
    'mediaAPI',
    'filesAPI',
    'membersAutoLogin',
    'buttonCard',
    'calloutCard',
    'nftCard'
];

module.exports.GA_KEYS = [...GA_FEATURES];
module.exports.WRITABLE_KEYS_ALLOWLIST = [...BETA_FEATURES, ...ALPHA_FEATURES];

module.exports.getAll = () => {
    const labs = _.cloneDeep(settingsCache.get('labs')) || {};

    ALPHA_FEATURES.forEach((alphaKey) => {
        if (labs[alphaKey] && !(config.get('enableDeveloperExperiments') || process.env.NODE_ENV.match(/^testing/))) {
            delete labs[alphaKey];
        }
    });

    GA_FEATURES.forEach((gaKey) => {
        labs[gaKey] = true;
    });

    labs.members = settingsCache.get('members_signup_access') !== 'none';

    return labs;
};

/**
 * @param {string} flag
 * @returns {boolean}
 */
module.exports.isSet = function isSet(flag) {
    const labsConfig = module.exports.getAll();

    return !!(labsConfig && labsConfig[flag] && labsConfig[flag] === true);
};

/**
 *
 * @param {object} options
 * @param {string} options.flagKey the internal lookup key of the flag e.g. labs.isSet(matchHelper)
 * @param {string} options.flagName the user-facing name of the flag e.g. Match helper
 * @param {string} options.helperName Name of the helper to be enabled/disabled
 * @param {string} [options.errorMessage] Optional replacement error message
 * @param {string} [options.errorContext] Optional replacement context message
 * @param {string} [options.errorHelp] Optional replacement help message
 * @param {string} [options.helpUrl] Url to show in the help message
 * @param {string} [options.async] is the helper async?
 * @param {function} callback
 * @returns {Promise<Handlebars.SafeString>|Handlebars.SafeString}
 */
module.exports.enabledHelper = function enabledHelper(options, callback) {
    const errDetails = {};
    let errString;

    if (module.exports.isSet(options.flagKey) === true) {
        // helper is active, use the callback
        return callback();
    }

    // Else, the helper is not active and we need to handle this as an error
    errDetails.message = tpl(options.errorMessage || messages.errorMessage, {helperName: options.helperName});
    errDetails.context = tpl(options.errorContext || messages.errorContext, {
        helperName: options.helperName,
        flagName: options.flagName
    });
    errDetails.help = tpl(options.errorHelp || messages.errorHelp, {url: options.helpUrl});

    logging.error(new errors.DisabledFeatureError(errDetails));

    const {SafeString} = require('express-hbs');
    errString = new SafeString(`<script>console.error("${_.values(errDetails).join(' ')}");</script>`);

    if (options.async) {
        return Promise.resolve(errString);
    }

    return errString;
};

module.exports.enabledMiddleware = flag => (req, res, next) => {
    if (module.exports.isSet(flag) === true) {
        return next();
    } else {
        return next(new errors.NotFoundError());
    }
};
<commit_msg>Added alpha flag for accordion card<commit_after>const _ = require('lodash');
const Promise = require('bluebird');
const errors = require('@tryghost/errors');
const logging = require('@tryghost/logging');
const tpl = require('@tryghost/tpl');

const settingsCache = require('./settings-cache');
const config = require('./config');

const messages = {
    errorMessage: 'The \\{\\{{helperName}\\}\\} helper is not available.',
    errorContext: 'The {flagName} flag must be enabled in labs if you wish to use the \\{\\{{helperName}\\}\\} helper.',
    errorHelp: 'See {url}'
};

// flags in this list always return `true`, allows quick global enable prior to full flag removal
const GA_FEATURES = [
    'customThemeSettings'
];

// NOTE: this allowlist is meant to be used to filter out any unexpected
//       input for the "labs" setting value
const BETA_FEATURES = [
    'activitypub',
    'multipleProducts'
];

const ALPHA_FEATURES = [
    'oauthLogin',
    'membersActivity',
    'cardSettingsPanel',
    'mediaAPI',
    'filesAPI',
    'membersAutoLogin',
    'buttonCard',
    'calloutCard',
    'nftCard',
    'accordionCard'
];

module.exports.GA_KEYS = [...GA_FEATURES];
module.exports.WRITABLE_KEYS_ALLOWLIST = [...BETA_FEATURES, ...ALPHA_FEATURES];

module.exports.getAll = () => {
    const labs = _.cloneDeep(settingsCache.get('labs')) || {};

    ALPHA_FEATURES.forEach((alphaKey) => {
        if (labs[alphaKey] && !(config.get('enableDeveloperExperiments') || process.env.NODE_ENV.match(/^testing/))) {
            delete labs[alphaKey];
        }
    });

    GA_FEATURES.forEach((gaKey) => {
        labs[gaKey] = true;
    });

    labs.members = settingsCache.get('members_signup_access') !== 'none';

    return labs;
};

/**
 * @param {string} flag
 * @returns {boolean}
 */
module.exports.isSet = function isSet(flag) {
    const labsConfig = module.exports.getAll();

    return !!(labsConfig && labsConfig[flag] && labsConfig[flag] === true);
};

/**
 *
 * @param {object} options
 * @param {string} options.flagKey the internal lookup key of the flag e.g. labs.isSet(matchHelper)
 * @param {string} options.flagName the user-facing name of the flag e.g. Match helper
 * @param {string} options.helperName Name of the helper to be enabled/disabled
 * @param {string} [options.errorMessage] Optional replacement error message
 * @param {string} [options.errorContext] Optional replacement context message
 * @param {string} [options.errorHelp] Optional replacement help message
 * @param {string} [options.helpUrl] Url to show in the help message
 * @param {string} [options.async] is the helper async?
 * @param {function} callback
 * @returns {Promise<Handlebars.SafeString>|Handlebars.SafeString}
 */
module.exports.enabledHelper = function enabledHelper(options, callback) {
    const errDetails = {};
    let errString;

    if (module.exports.isSet(options.flagKey) === true) {
        // helper is active, use the callback
        return callback();
    }

    // Else, the helper is not active and we need to handle this as an error
    errDetails.message = tpl(options.errorMessage || messages.errorMessage, {helperName: options.helperName});
    errDetails.context = tpl(options.errorContext || messages.errorContext, {
        helperName: options.helperName,
        flagName: options.flagName
    });
    errDetails.help = tpl(options.errorHelp || messages.errorHelp, {url: options.helpUrl});

    logging.error(new errors.DisabledFeatureError(errDetails));

    const {SafeString} = require('express-hbs');
    errString = new SafeString(`<script>console.error("${_.values(errDetails).join(' ')}");</script>`);

    if (options.async) {
        return Promise.resolve(errString);
    }

    return errString;
};

module.exports.enabledMiddleware = flag => (req, res, next) => {
    if (module.exports.isSet(flag) === true) {
        return next();
    } else {
        return next(new errors.NotFoundError());
    }
};
<|endoftext|>


<commit_before>const gulp = require('gulp');
const rollup = require('gulp-rollup');
const babel = require('gulp-babel');
const eslint = require('gulp-eslint');
const uglify = require('gulp-uglify');
const autoprefixer = require('gulp-autoprefixer');
const cssnano = require('gulp-cssnano');
const sass = require('gulp-sass');
const imagemin = require('gulp-imagemin');
const qiniu = require('gulp-qiniu');
const replace = require('gulp-replace');
const config = require('../config');

const srcRegs = {
  jsInHtml: /<\s*script\s+.*src\s*=\s*["|']([^"']+)[^>]*><\s*\/\s*script\s*>/gim,
  dataSrcInHtml: /<\s*script\s+.*data\-src\s*=\s*["|']([^"']+)[^>]*><\s*\/\s*script\s*>/gim,
  cssInHtml: /<\s*link\s+.*href\s*=\s*["|']([^"']+)[^>]*>/gim,
  imageInHtml: /<\s*img\s+.*src\s*=\s*["|']([^"']+)[^>]*>/gim,
  imageInCSS: /url\s*\(\s*['|"]?([^'")]+)['|"]?\s*\)/gim
};

let timestamp = '';
const replaceRegs = {
  absoluteLink: /^(http(s)?\:)?\/\/\w+/ig,
  relativeLink: /^[\.{0,2}\/]*(\w+)/ig,
  joiner: /\w+\?\w+=\w+/gim
};

function replaceLink(target, link) {
  if (replaceRegs.absoluteLink.test(link)) {
    return target;
  }
  const joiner = replaceRegs.joiner.test(link) ? '&' : '?';
  const absoluteLink = link.replace(replaceRegs.relativeLink, config.CDNBaseUrl + '$1') + joiner + 't=' + timestamp;
  return target.replace(link, absoluteLink);
}

gulp.task('deploy:html', () => {
  return gulp.src('./src/**/*.html')
    .pipe(gulp.dest('./dist'));
});

gulp.task('deploy:css', () => {
  return gulp.src('./src/scss/main.scss')
    .pipe(sass().on('error', sass.logError))
    .pipe(autoprefixer({
      browsers: ['last 4 versions', 'Android >= 4.4']
    }))
    .pipe(cssnano())
    .pipe(gulp.dest('./dist/styles'));
});

gulp.task('deploy:libjs', () => {
  return gulp.src('./src/javascripts/libs/**/*.js')
    .pipe(gulp.dest('./dist/javascripts/libs'));
});

gulp.task('deploy:js', () => {
  return gulp.src('./src/javascripts/**/*.js')
    .pipe(rollup({
      entry: './src/javascripts/main.js',
      format: 'iife'
    }))
    .pipe(babel({
      presets: ['es2015', 'stage-2']
    }))
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(eslint.failAfterError())
    .pipe(uglify())
    .pipe(gulp.dest('./dist/javascripts'));
});

gulp.task('deploy:images', () => {
  return gulp.src('./src/images/**/*')
    .pipe(imagemin())
    .pipe(gulp.dest('./dist/images'));
});

gulp.task('deploy:cdn', ['deploy:html', 'deploy:css'], () => {
  timestamp = new Date().getTime();

  return gulp.src('./dist/**/*.{html,css}')
    .pipe(replace(srcRegs.jsInHtml, replaceLink))
    .pipe(replace(srcRegs.cssInHtml, replaceLink))
    .pipe(replace(srcRegs.imageInHtml, replaceLink))
    .pipe(replace(srcRegs.dataSrcInHtml, replaceLink))
    .pipe(replace(srcRegs.imageInCSS, replaceLink))
    .pipe(gulp.dest('./dist'));
});

gulp.task('deploy:upload', ['deploy:cdn', 'deploy:images', 'deploy:libjs', 'deploy:js'], () => {
  return gulp.src('./dist/**/*')
    .pipe(qiniu(config.qiniu.config, {
      dir: config.qiniu.dir,
      versioning: false
    }));
});

gulp.task('deploy:staging', ['deploy:html', 'deploy:css', 'deploy:images', 'deploy:libjs', 'deploy:js']);

gulp.task('deploy:production', () => gulp.start('deploy:upload'));

module.exports = {
  staging: function () {
    return gulp.start('deploy:staging');
  },
  production: function () {
    return gulp.start('deploy:production');
  }
};
<commit_msg>增加px2rem插件<commit_after>const gulp = require('gulp');
const rollup = require('gulp-rollup');
const babel = require('gulp-babel');
const eslint = require('gulp-eslint');
const uglify = require('gulp-uglify');
const autoprefixer = require('gulp-autoprefixer');
const cssnano = require('gulp-cssnano');
const sass = require('gulp-sass');
const px2rem = require('gulp-px2rem');
const imagemin = require('gulp-imagemin');
const qiniu = require('gulp-qiniu');
const replace = require('gulp-replace');
const config = require('../config');

const srcRegs = {
  jsInHtml: /<\s*script\s+.*src\s*=\s*["|']([^"']+)[^>]*><\s*\/\s*script\s*>/gim,
  dataSrcInHtml: /<\s*script\s+.*data\-src\s*=\s*["|']([^"']+)[^>]*><\s*\/\s*script\s*>/gim,
  cssInHtml: /<\s*link\s+.*href\s*=\s*["|']([^"']+)[^>]*>/gim,
  imageInHtml: /<\s*img\s+.*src\s*=\s*["|']([^"']+)[^>]*>/gim,
  imageInCSS: /url\s*\(\s*['|"]?([^'")]+)['|"]?\s*\)/gim
};

let timestamp = '';
const replaceRegs = {
  absoluteLink: /^(http(s)?\:)?\/\/\w+/ig,
  relativeLink: /^[\.{0,2}\/]*(\w+)/ig,
  joiner: /\w+\?\w+=\w+/gim
};

function replaceLink(target, link) {
  if (replaceRegs.absoluteLink.test(link)) {
    return target;
  }
  const joiner = replaceRegs.joiner.test(link) ? '&' : '?';
  const absoluteLink = link.replace(replaceRegs.relativeLink, config.CDNBaseUrl + '$1') + joiner + 't=' + timestamp;
  return target.replace(link, absoluteLink);
}

gulp.task('deploy:html', () => {
  return gulp.src('./src/**/*.html')
    .pipe(gulp.dest('./dist'));
});

gulp.task('deploy:css', () => {
  return gulp.src('./src/scss/main.scss')
    .pipe(sass().on('error', sass.logError))
    .pipe(autoprefixer({
      browsers: ['last 4 versions', 'Android >= 4.4']
    }))
    .pipe(px2rem())
    .pipe(cssnano())
    .pipe(gulp.dest('./dist/styles'));
});

gulp.task('deploy:libjs', () => {
  return gulp.src('./src/javascripts/libs/**/*.js')
    .pipe(gulp.dest('./dist/javascripts/libs'));
});

gulp.task('deploy:js', () => {
  return gulp.src('./src/javascripts/**/*.js')
    .pipe(rollup({
      entry: './src/javascripts/main.js',
      format: 'iife'
    }))
    .pipe(babel({
      presets: ['es2015', 'stage-2']
    }))
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(eslint.failAfterError())
    .pipe(uglify())
    .pipe(gulp.dest('./dist/javascripts'));
});

gulp.task('deploy:images', () => {
  return gulp.src('./src/images/**/*')
    .pipe(imagemin())
    .pipe(gulp.dest('./dist/images'));
});

gulp.task('deploy:cdn', ['deploy:html', 'deploy:css'], () => {
  timestamp = new Date().getTime();

  return gulp.src('./dist/**/*.{html,css}')
    .pipe(replace(srcRegs.jsInHtml, replaceLink))
    .pipe(replace(srcRegs.cssInHtml, replaceLink))
    .pipe(replace(srcRegs.imageInHtml, replaceLink))
    .pipe(replace(srcRegs.dataSrcInHtml, replaceLink))
    .pipe(replace(srcRegs.imageInCSS, replaceLink))
    .pipe(gulp.dest('./dist'));
});

gulp.task('deploy:upload', ['deploy:cdn', 'deploy:images', 'deploy:libjs', 'deploy:js'], () => {
  return gulp.src('./dist/**/*')
    .pipe(qiniu(config.qiniu.config, {
      dir: config.qiniu.dir,
      versioning: false
    }));
});

gulp.task('deploy:build', ['deploy:html', 'deploy:css', 'deploy:images', 'deploy:libjs', 'deploy:js']);

gulp.task('deploy:staging', ['deploy:build']);

gulp.task('deploy:production', ['deploy:build'], () => gulp.start('deploy:upload'));

module.exports = {
  staging: function () {
    return gulp.start('deploy:staging');
  },
  production: function () {
    return gulp.start('deploy:production');
  }
};
<|endoftext|>


<commit_before>const Stream = require('stream');
const Util = require('util');

/**
 * @const
 * @type {RegExp}
 */
const PARSE_FLOAT_TEST = /^[0-9]+(?:\.[0-9]*)?(?:[eE]\+[0-9]+)?$|^(?:[0-9]+)?\.[0-9]+(?:e+[0-9]+)?$|^[-+]?Infinity$|^[-+]?NaN$/;

const Transform = Stream.Transform;

/**
 * @param {Object?} options
 * @param {string} [options.delimiter=','] - Specify what is the CSV delimeter
 * @param {boolean} [options.multiline=true] - Support Excel-like multiline CSV
 * @param {boolean} [options.allowQuotes=true] - Allow quotation marks to wrap columns
 * @param {boolean} [options.skipEmptyLines=false] - Should empty lines be automatically skipped?
 * @param {boolean} [options.parseNumbers=false] - Automatically parse numbers (with a . as the decimal separator)
 * @param {boolean} [options.parseBooleans=false] - Automatically parse booleans (strictly lowercase `true` and `false`)
 * @param {boolean} [options.ltrim=false] - Automatically left-trims columns
 * @param {boolean} [options.rtrim=false] - Automatically right-trims columns
 * @param {boolean} [options.trim=false] - If true, then both 'ltrim' and 'rtrim' are set to true
 * @param {boolean} [options.skipHeader=false] - If true, then skip the first header row
 * @returns {CsvReadableStream}
 * @constructor
 */
const CsvReadableStream = function (options) {
    options = options || {};

    //noinspection JSUndefinedPropertyAssignment
    options.objectMode = true;

    if (!(this instanceof CsvReadableStream)) {
        return new CsvReadableStream(options);
    }

    let data = null,
        dataIndex = null,
        nextIndex = null,
        dataLen = null,
        columns = [],
        column = '',
        lastLineEndCR = false,
        lookForBOM = true,
        isQuoted = false,
        rowCount = 0;

    const multiline = !!options.multiline || typeof options.multiline === 'undefined',
        delimiter = options.delimiter != null ? options.delimiter.toString() || ',' : ',',
        allowQuotes = !!options.allowQuotes || typeof options.allowQuotes === 'undefined',
        skipEmptyLines = !!options.skipEmptyLines,
        parseNumbers = !!options.parseNumbers,
        parseBooleans = !!options.parseBooleans,
        ltrim = !!options.ltrim || !!options.trim,
        rtrim = !!options.rtrim || !!options.trim,
        trim = options.ltrim && options.rtrim,
        skipHeader = options.skipHeader,

        postProcessingEnabled = parseNumbers || parseBooleans || ltrim || rtrim;

    const postProcessColumn = function (column) {

        if (trim) {
            column = column.replace(/^\s+|\s+$/, '');
        } else if (ltrim) {
            column = column.replace(/^\s+/, '');
        } else if (rtrim) {
            column = column.replace(/\s+$/, '');
        }

        if (parseBooleans) {
            if (column === 'true') {
                return true;
            }
            if (column === 'false') {
                return false;
            }
        }

        if (parseNumbers) {
            if (PARSE_FLOAT_TEST.test(column)) {
                return parseFloat(column);
            }
        }

        return column;
    };

    this._processChunk = function (newData) {

        if (newData) {
            if (data) {
                data = data.substr(dataIndex) + newData;
            } else {
                data = newData;
            }
            dataLen = data.length;
            dataIndex = 0;
        }

        // Node doesn't strip BOMs, that's in user's land
        if (lookForBOM) {
            if (newData.charCodeAt(0) === 0xfeff) {
                dataIndex++;
            }
            lookForBOM = false;
        }

        let isFinishedLine = false;

        for (; dataIndex < dataLen; dataIndex++) {
            const c = data[dataIndex];

            if (c === '\n' || c === '\r') {
                if (!isQuoted || !multiline) {
                    if (lastLineEndCR && c === '\n') {
                        lastLineEndCR = false;
                        continue;
                    }
                    lastLineEndCR = c === '\r';
                    dataIndex++;
                    isFinishedLine = true;
                    rowCount++;

                    if (!multiline) {
                        isQuoted = false;
                    }

                    break;
                }
            }

            if (isQuoted) {
                if (c === '"') {
                    nextIndex = dataIndex + 1;

                    // Do we have enough data to peek at the next character?
                    if (nextIndex >= dataLen && !this._isStreamDone) {
                        // Wait for more data to arrive
                        break;
                    }

                    if (nextIndex < dataLen && data[nextIndex] === '"') {
                        column += '"';
                        dataIndex++;
                    } else {
                        isQuoted = false;
                    }
                } else {
                    column += c;
                }
            } else {
                if (c === delimiter) {
                    columns.push(column);
                    column = '';
                } else if (c === '"' && allowQuotes) {
                    if (column.length) {
                        column += c;
                    } else {
                        isQuoted = true;
                    }
                } else {
                    column += c;
                }
            }
        }

        if (dataIndex === dataLen) {
            data = null;
        }

        if (isFinishedLine && skipHeader && rowCount === 1) {
            column = '';
            columns = [];
            // Look to see if there are more rows in available data
            this._processChunk();
            return;
        } else if (isFinishedLine || (data === null && this._isStreamDone)) {

            if (columns.length || column || data || !this._isStreamDone) {

                // We have a row, send it to the callback

                // Commit this row
                columns.push(column);
                const row = columns;

                // Clear row state data
                columns = [];
                column = '';
                isQuoted = false;

                // Is this row full or empty?
                if (row.length > 1 || row[0].length || !skipEmptyLines) {

                    // Post processing
                    if (postProcessingEnabled) {
                        let i = 0;
                        const rowSize = row.length;
                        for (; i < rowSize; i++) {
                            row[i] = postProcessColumn(row[i]);
                        }
                    }

                    // Emit the parsed row
                    //noinspection JSUnresolvedFunction
                    this.push(row);
                }

                // Look to see if there are more rows in available data
                this._processChunk();

            } else {
                // We just ran into a newline at the end of the file, ignore it
            }

        } else {

            if (data) {

                // Let more data come in.
                // We are probably waiting for a "peek" at the next character

            } else {

                // We have probably hit end of file.
                // Let the end event come in.

            }

        }

    };

    Transform.call(this, options);
};

Util.inherits(CsvReadableStream, Transform);

//noinspection JSUnusedGlobalSymbols
CsvReadableStream.prototype._transform = function (chunk, enc, cb) {

    this._processChunk(chunk);

    cb();
};

//noinspection JSUnusedGlobalSymbols
CsvReadableStream.prototype._flush = function (cb) {

    this._isStreamDone = true;

    this._processChunk();

    cb();
};

/**
 * @module
 * @type {CsvReadableStream}
 */
module.exports = CsvReadableStream;
<commit_msg>Correctly consider ltrim/rtrim default (fixes #8)<commit_after>const Stream = require('stream');
const Util = require('util');

/**
 * @const
 * @type {RegExp}
 */
const PARSE_FLOAT_TEST = /^[0-9]+(?:\.[0-9]*)?(?:[eE]\+[0-9]+)?$|^(?:[0-9]+)?\.[0-9]+(?:e+[0-9]+)?$|^[-+]?Infinity$|^[-+]?NaN$/;

const Transform = Stream.Transform;

/**
 * @param {Object?} options
 * @param {string} [options.delimiter=','] - Specify what is the CSV delimeter
 * @param {boolean} [options.multiline=true] - Support Excel-like multiline CSV
 * @param {boolean} [options.allowQuotes=true] - Allow quotation marks to wrap columns
 * @param {boolean} [options.skipEmptyLines=false] - Should empty lines be automatically skipped?
 * @param {boolean} [options.parseNumbers=false] - Automatically parse numbers (with a . as the decimal separator)
 * @param {boolean} [options.parseBooleans=false] - Automatically parse booleans (strictly lowercase `true` and `false`)
 * @param {boolean} [options.ltrim=false] - Automatically left-trims columns
 * @param {boolean} [options.rtrim=false] - Automatically right-trims columns
 * @param {boolean} [options.trim=false] - If true, then both 'ltrim' and 'rtrim' are set to true
 * @param {boolean} [options.skipHeader=false] - If true, then skip the first header row
 * @returns {CsvReadableStream}
 * @constructor
 */
const CsvReadableStream = function (options) {
    options = options || {};

    //noinspection JSUndefinedPropertyAssignment
    options.objectMode = true;

    if (!(this instanceof CsvReadableStream)) {
        return new CsvReadableStream(options);
    }

    let data = null,
        dataIndex = null,
        nextIndex = null,
        dataLen = null,
        columns = [],
        column = '',
        lastLineEndCR = false,
        lookForBOM = true,
        isQuoted = false,
        rowCount = 0;

    const multiline = !!options.multiline || typeof options.multiline === 'undefined',
        delimiter = options.delimiter != null ? options.delimiter.toString() || ',' : ',',
        allowQuotes = !!options.allowQuotes || typeof options.allowQuotes === 'undefined',
        skipEmptyLines = !!options.skipEmptyLines,
        parseNumbers = !!options.parseNumbers,
        parseBooleans = !!options.parseBooleans,
        ltrim = !!options.ltrim || !!options.trim,
        rtrim = !!options.rtrim || !!options.trim,
        trim = ltrim && rtrim,
        skipHeader = options.skipHeader,

        postProcessingEnabled = parseNumbers || parseBooleans || ltrim || rtrim;

    const postProcessColumn = function (column) {

        if (trim) {
            column = column.replace(/^\s+|\s+$/, '');
        } else if (ltrim) {
            column = column.replace(/^\s+/, '');
        } else if (rtrim) {
            column = column.replace(/\s+$/, '');
        }

        if (parseBooleans) {
            if (column === 'true') {
                return true;
            }
            if (column === 'false') {
                return false;
            }
        }

        if (parseNumbers) {
            if (PARSE_FLOAT_TEST.test(column)) {
                return parseFloat(column);
            }
        }

        return column;
    };

    this._processChunk = function (newData) {

        if (newData) {
            if (data) {
                data = data.substr(dataIndex) + newData;
            } else {
                data = newData;
            }
            dataLen = data.length;
            dataIndex = 0;
        }

        // Node doesn't strip BOMs, that's in user's land
        if (lookForBOM) {
            if (newData.charCodeAt(0) === 0xfeff) {
                dataIndex++;
            }
            lookForBOM = false;
        }

        let isFinishedLine = false;

        for (; dataIndex < dataLen; dataIndex++) {
            const c = data[dataIndex];

            if (c === '\n' || c === '\r') {
                if (!isQuoted || !multiline) {
                    if (lastLineEndCR && c === '\n') {
                        lastLineEndCR = false;
                        continue;
                    }
                    lastLineEndCR = c === '\r';
                    dataIndex++;
                    isFinishedLine = true;
                    rowCount++;

                    if (!multiline) {
                        isQuoted = false;
                    }

                    break;
                }
            }

            if (isQuoted) {
                if (c === '"') {
                    nextIndex = dataIndex + 1;

                    // Do we have enough data to peek at the next character?
                    if (nextIndex >= dataLen && !this._isStreamDone) {
                        // Wait for more data to arrive
                        break;
                    }

                    if (nextIndex < dataLen && data[nextIndex] === '"') {
                        column += '"';
                        dataIndex++;
                    } else {
                        isQuoted = false;
                    }
                } else {
                    column += c;
                }
            } else {
                if (c === delimiter) {
                    columns.push(column);
                    column = '';
                } else if (c === '"' && allowQuotes) {
                    if (column.length) {
                        column += c;
                    } else {
                        isQuoted = true;
                    }
                } else {
                    column += c;
                }
            }
        }

        if (dataIndex === dataLen) {
            data = null;
        }

        if (isFinishedLine && skipHeader && rowCount === 1) {
            column = '';
            columns = [];
            // Look to see if there are more rows in available data
            this._processChunk();
            return;
        } else if (isFinishedLine || (data === null && this._isStreamDone)) {

            if (columns.length || column || data || !this._isStreamDone) {

                // We have a row, send it to the callback

                // Commit this row
                columns.push(column);
                const row = columns;

                // Clear row state data
                columns = [];
                column = '';
                isQuoted = false;

                // Is this row full or empty?
                if (row.length > 1 || row[0].length || !skipEmptyLines) {

                    // Post processing
                    if (postProcessingEnabled) {
                        let i = 0;
                        const rowSize = row.length;
                        for (; i < rowSize; i++) {
                            row[i] = postProcessColumn(row[i]);
                        }
                    }

                    // Emit the parsed row
                    //noinspection JSUnresolvedFunction
                    this.push(row);
                }

                // Look to see if there are more rows in available data
                this._processChunk();

            } else {
                // We just ran into a newline at the end of the file, ignore it
            }

        } else {

            if (data) {

                // Let more data come in.
                // We are probably waiting for a "peek" at the next character

            } else {

                // We have probably hit end of file.
                // Let the end event come in.

            }

        }

    };

    Transform.call(this, options);
};

Util.inherits(CsvReadableStream, Transform);

//noinspection JSUnusedGlobalSymbols
CsvReadableStream.prototype._transform = function (chunk, enc, cb) {

    this._processChunk(chunk);

    cb();
};

//noinspection JSUnusedGlobalSymbols
CsvReadableStream.prototype._flush = function (cb) {

    this._isStreamDone = true;

    this._processChunk();

    cb();
};

/**
 * @module
 * @type {CsvReadableStream}
 */
module.exports = CsvReadableStream;
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var through = require('through2');
var PassThrough = require('stream').PassThrough;
var asyncFilter = require('async-filter');
var tryall = require('tryall');
var mkdirp = require('mkdirp');

/**
 * Create a new StreamCache (or attach to an existing one)
 *
 * @constructor
 * @param {String} rootDirectory The directory in which to store cached objects.
 */
function StreamCache(rootDirectory) {
	this.rootCacheDirectory = rootDirectory;

	// TODO: Where's best to handle errors here?
	// Ensure the rootCacheDirectory is present.
	this.ready = new Promise(function(resolve, reject) {
		mkdirp(this.rootCacheDirectory, function(e) {
			if (e) {
				reject(e);
			} else {
				resolve();
			}
		});
	}.bind(this));
}

/**
 * Get a Stream from the cache, if the stream doesn't exist, create it via the
 * callback and cache it.
 *
 * @param {String} identifier                     The cache key.
 * @param {Object} options                        The options object.
 * @param {Function -> ReadStream} createCallback A function that can be used to create a new object if the cache key is empty.
 * @return {Promise -> ReadStream}
 */
StreamCache.prototype.get = function(identifier, options, createCallback) {

	var cache = this;

	return new Promise(function(resolve, reject) {
		cache.read(identifier, options).then(function(stream) {
			if (stream) {
				resolve(stream);
			} else {
				resolve(cache.writeThrough(identifier, createCallback()));
			}
		});
	});
};

/**
 * Purge the cache based on the filter function.  The function will take a
 * single argument of the cache key.  The function can then return true, if the
 * key should remain in the cache or false if the cache key should be purged.
 *
 * @example Purge everything in the cache
 *
 * cache.purge(function() { return true; });
 *
 * @param {Function} callback The filter function to determine whether the
 *                            cache key should be purged.
 */
StreamCache.prototype.purge = function(callback) {
	var cache = this;
	return new Promise(function(resolve, reject) {
		// Read the files that exist in the cache directory
		fs.readdir(cache.rootCacheDirectory, function(err, files) {
			if (err) {
				reject(err);
			}

			// Filter based on the callback function
			var filesToPurgePromise = asyncFilter(files, function(item) {
				var callbackResult = callback(item);
				return Promise.resolve(callbackResult);
			});

			// Try and purge all of the cached objects.  `tryall` will try
			// every promise and collect erroring promises as part of it's
			// resolved value.
			resolve(filesToPurgePromise.then(function(filesToPurge) {
				return tryall(filesToPurge.map(function(file) {
					return new Promise(function(innerResolve, innerReject) {
						fs.unlink(cache._getCachedObjectPath(file), function(err) {
							if (err) {
								innerReject(err);
							} else {
								innerResolve(true);
							}
						});
					});
				}));
			}));
		});
	});
};

StreamCache.prototype.read = function(identifier, options) {
	options = options || {};
	var cachedObjectPath = this._getCachedObjectPath(identifier);

	var newerThan = options.newerThan || 0;

	return new Promise(function(resolve, reject) {
		fs.open(cachedObjectPath, 'r', function(e, fd) {
			if (e) {
				resolve(false);
			} else {
				fs.fstat(fd, function(e, stats) {
					if (stats.mtime < newerThan) {
						resolve(false);
					} else {
						// First argument of fs.createReadStream is ignored
						// because the 'fd' is present
						var readStream = fs.createReadStream(false, { fd: fd });
						readStream.on('end', function() {
							// Close fd
							fs.close(fd);
						});
						resolve(readStream);
					}
				});
			}
		});
	});
};

StreamCache.prototype.set = function(identifier, stream) {
	var cachedObjectPath = this._getCachedObjectPath(identifier);
	var cacheFile = fs.createWriteStream(cachedObjectPath);

	stream.pipe(cacheFile);
};

StreamCache.prototype.writeThrough = function(identifier, stream) {
	var cachedObjectPath = this._getCachedObjectPath(identifier);

	var cacheStream = this.ready.then(function() {
		return fs.createWriteStream(cachedObjectPath);
	});

	return stream.pipe(through(function(chunk, enc, callback) {
		cacheStream.then(function(cache) {
			cache.write(chunk, enc);
		});

		this.push(chunk, enc);
		callback();
	}, function flush(callback) {
		cacheStream.then(function(stream) {
			stream.end();
			callback();
		});
	}));
};

StreamCache.prototype._getCachedObjectPath = function(identifier) {

	// resolving the identifier against the root filesystem directory prevents directory
	// traversal attacks
	return path.join(this.rootCacheDirectory, path.resolve('/', identifier));
};

module.exports = StreamCache;
<commit_msg>remove set api<commit_after>var fs = require('fs');
var path = require('path');
var through = require('through2');
var PassThrough = require('stream').PassThrough;
var asyncFilter = require('async-filter');
var tryall = require('tryall');
var mkdirp = require('mkdirp');

/**
 * Create a new StreamCache (or attach to an existing one)
 *
 * @constructor
 * @param {String} rootDirectory The directory in which to store cached objects.
 */
function StreamCache(rootDirectory) {
	this.rootCacheDirectory = rootDirectory;

	// TODO: Where's best to handle errors here?
	// Ensure the rootCacheDirectory is present.
	this.ready = new Promise(function(resolve, reject) {
		mkdirp(this.rootCacheDirectory, function(e) {
			if (e) {
				reject(e);
			} else {
				resolve();
			}
		});
	}.bind(this));
}

/**
 * Get a Stream from the cache, if the stream doesn't exist, create it via the
 * callback and cache it.
 *
 * @param {String} identifier                     The cache key.
 * @param {Object} options                        The options object.
 * @param {Function -> ReadStream} createCallback A function that can be used to create a new object if the cache key is empty.
 * @return {Promise -> ReadStream}
 */
StreamCache.prototype.get = function(identifier, options, createCallback) {

	var cache = this;

	return new Promise(function(resolve, reject) {
		cache.read(identifier, options).then(function(stream) {
			if (stream) {
				resolve(stream);
			} else {
				resolve(cache.writeThrough(identifier, createCallback()));
			}
		});
	});
};

/**
 * Purge the cache based on the filter function.  The function will take a
 * single argument of the cache key.  The function can then return true, if the
 * key should remain in the cache or false if the cache key should be purged.
 *
 * @example Purge everything in the cache
 *
 * cache.purge(function() { return true; });
 *
 * @param {Function} callback The filter function to determine whether the
 *                            cache key should be purged.
 */
StreamCache.prototype.purge = function(callback) {
	var cache = this;
	return new Promise(function(resolve, reject) {
		// Read the files that exist in the cache directory
		fs.readdir(cache.rootCacheDirectory, function(err, files) {
			if (err) {
				reject(err);
			}

			// Filter based on the callback function
			var filesToPurgePromise = asyncFilter(files, function(item) {
				var callbackResult = callback(item);
				return Promise.resolve(callbackResult);
			});

			// Try and purge all of the cached objects.  `tryall` will try
			// every promise and collect erroring promises as part of it's
			// resolved value.
			resolve(filesToPurgePromise.then(function(filesToPurge) {
				return tryall(filesToPurge.map(function(file) {
					return new Promise(function(innerResolve, innerReject) {
						fs.unlink(cache._getCachedObjectPath(file), function(err) {
							if (err) {
								innerReject(err);
							} else {
								innerResolve(true);
							}
						});
					});
				}));
			}));
		});
	});
};

StreamCache.prototype.read = function(identifier, options) {
	options = options || {};
	var cachedObjectPath = this._getCachedObjectPath(identifier);

	var newerThan = options.newerThan || 0;

	return new Promise(function(resolve, reject) {
		fs.open(cachedObjectPath, 'r', function(e, fd) {
			if (e) {
				resolve(false);
			} else {
				fs.fstat(fd, function(e, stats) {
					if (stats.mtime < newerThan) {
						resolve(false);
					} else {
						// First argument of fs.createReadStream is ignored
						// because the 'fd' is present
						var readStream = fs.createReadStream(false, { fd: fd });
						readStream.on('end', function() {
							// Close fd
							fs.close(fd);
						});
						resolve(readStream);
					}
				});
			}
		});
	});
};

StreamCache.prototype.writeThrough = function(identifier, stream) {
	var cachedObjectPath = this._getCachedObjectPath(identifier);

	var cacheStream = this.ready.then(function() {
		return fs.createWriteStream(cachedObjectPath);
	});

	return stream.pipe(through(function(chunk, enc, callback) {
		cacheStream.then(function(cache) {
			cache.write(chunk, enc);
		});

		this.push(chunk, enc);
		callback();
	}, function flush(callback) {
		cacheStream.then(function(stream) {
			stream.end();
			callback();
		});
	}));
};

StreamCache.prototype._getCachedObjectPath = function(identifier) {

	// resolving the identifier against the root filesystem directory prevents directory
	// traversal attacks
	return path.join(this.rootCacheDirectory, path.resolve('/', identifier));
};

module.exports = StreamCache;
<|endoftext|>


<commit_before>const lgtv2 = require('lgtv2');
const wol = require('wake_on_lan');
const tcpp = require('tcp-ping');

let Service, Characteristic;

module.exports = function (homebridge) {
    Service = homebridge.hap.Service;
    Characteristic = homebridge.hap.Characteristic;

    homebridge.registerAccessory('homebridge-webos3', 'webos3', webos3Accessory);
};

function webos3Accessory(log, config, api) {
    this.log = log;
    this.ip = config['ip'];
    this.name = config['name'];
    this.mac = config['mac'];
    this.keyFile = config['keyFile'];
    this.volumeControl = config['volumeControl'];
    if (this.volumeControl == undefined) {
        this.volumeControl = true;
    }
    this.pollingEnabled = config['pollingEnabled'];
    if (this.pollingEnabled == undefined) {
        this.pollingEnabled = false;
    }
    this.alivePollingInterval = config['pollingInterval'] || 5;
    this.alivePollingInterval = this.alivePollingInterval * 1000;
    this.externalSourceSwitch = config['externalSourceSwitch'];
    this.appSwitch = config['appSwitch'];

    this.url = 'ws://' + this.ip + ':3000';
    this.enabledServices = [];
    this.connected = false;
    this.checkCount = 0;
    this.checkAliveInterval = null;

    lgtv = new lgtv2({
        url: this.url,
        timeout: 5000,
        reconnect: 3000,
        keyFile: this.keyFile
    });

    lgtv.on('connect', () => {
        this.log.info('webOS - connected to TV');
        this.connected = true;
        if (!this.checkAliveInterval && this.pollingEnabled) {
            this.checkAliveInterval = setInterval(this.checkTVState.bind(this, this.pollCallback.bind(this)), this.alivePollingInterval);
        }
        lgtv.subscribe('ssap://com.webos.applicationManager/getForegroundAppInfo', (err, res) => {
            if (res && res.appId) {
                this.log.info('webOS - current appId: %s', res.appId);
            }
        });
        this.updateAccessoryStatus();
    });

    lgtv.on('close', () => {
        this.log.info('webOS - disconnected from TV');
        this.connected = false;
        //if(this.checkAliveInterval) {
        //  clearInterval(this.checkAliveInterval);
        //  this.checkAliveInterval = undefined;
        //}
    });

    lgtv.on('error', (error) => {
        this.log.error('webOS - %s', error);
        //this.connected = false;
        //setTimeout(lgtv.connect(this.url), 5000);
    });

    lgtv.on('prompt', () => {
        this.log.info('webOS - prompt for confirmation');
        this.connected = false;
    });

    lgtv.on('connecting', () => {
        this.log.debug('webOS - connecting to TV');
        this.connected = false;
    });

    this.powerService = new Service.Switch(this.name + " Power", "powerService");
    this.volumeService = new Service.Lightbulb(this.name + " Volume", "volumeService");
    this.externalSourceSwitchService = new Service.Switch(this.name + " Input: " + this.externalSourceSwitch, "externalSourceSwitchService");
    this.appSwitchService = new Service.Switch(this.name + " App: " + this.appSwitch, "appSwitchService");
    this.informationService = new Service.AccessoryInformation();

    this.powerService
        .getCharacteristic(Characteristic.On)
        .on('get', this.getState.bind(this))
        .on('set', this.setState.bind(this));

    this.volumeService
        .getCharacteristic(Characteristic.On)
        .on('get', this.getMuteState.bind(this))
        .on('set', this.setMuteState.bind(this));

    this.volumeService
        .addCharacteristic(new Characteristic.Brightness())
        .on('get', this.getVolume.bind(this))
        .on('set', this.setVolume.bind(this));

    this.externalSourceSwitchService
        .getCharacteristic(Characteristic.On)
        .on('get', this.getExternalSourceSwitchState.bind(this))
        .on('set', this.setExternalSourceSwitchState.bind(this));

    this.appSwitchService
        .getCharacteristic(Characteristic.On)
        .on('get', this.getAppSwitchState.bind(this))
        .on('set', this.setAppSwitchState.bind(this));

    this.informationService
        .setCharacteristic(Characteristic.Manufacturer, 'LG Electronics Inc.')
        .setCharacteristic(Characteristic.Model, 'webOS TV')
        .setCharacteristic(Characteristic.SerialNumber, '-')
        .setCharacteristic(Characteristic.FirmwareRevision, '0.9.2');

    this.enabledServices.push(this.powerService);
    if (this.volumeControl) this.enabledServices.push(this.volumeService);
    if (this.externalSourceSwitch && this.externalSourceSwitch.length > 0) this.enabledServices.push(this.externalSourceSwitchService);
    if (this.appSwitch && this.appSwitch.length > 0) this.enabledServices.push(this.appSwitchService);
    this.enabledServices.push(this.informationService);

}

webos3Accessory.prototype.setMuteStateManuallyCallback = function (error, value) {
    if (this.volumeControl) this.volumeService.getCharacteristic(Characteristic.On).updateValue(value);
};

webos3Accessory.prototype.setExternalSourceSwitchManuallyCallback = function (error, value) {
    if (this.externalSourceSwitch && this.externalSourceSwitch.length > 0) this.externalSourceSwitchService.getCharacteristic(Characteristic.On).updateValue(value);
};

webos3Accessory.prototype.setAppSwitchManuallyCallback = function (error, value) {
    if (this.appSwitch && this.appSwitch.length > 0) this.appSwitchService.getCharacteristic(Characteristic.On).updateValue(value);
};

webos3Accessory.prototype.updateAccessoryStatus = function () {
    if (this.volumeControl) this.checkMuteState(this.setMuteStateManuallyCallback.bind(this));
    if (this.externalSourceSwitch && this.externalSourceSwitch.length > 0) this.checkExternalInput(this.setExternalSourceSwitchManuallyCallback.bind(this));
    if (this.appSwitch && this.appSwitch.length > 0) this.checkForegroundApp(this.setAppSwitchManuallyCallback.bind(this));
};

webos3Accessory.prototype.pollCallback = function (error, status) {
    if (!status) {
        this.powerService.getCharacteristic(Characteristic.On).updateValue(status);
        this.volumeService.getCharacteristic(Characteristic.On).updateValue(status);
    } else {
        this.powerService.getCharacteristic(Characteristic.On).updateValue(status);
    }
};

webos3Accessory.prototype.checkTVState = function (callback) {
    tcpp.probe(this.ip, 3000, (err, isAlive) => {
        if (!isAlive) {
            this.connected = false;
        } else {
            this.connected = true;
        }
        this.log.debug('webOS - TV state: %s', this.connected ? "On" : "Off");
        callback(null, this.connected);
    });
};

webos3Accessory.prototype.checkMuteState = function (callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/getStatus', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - TV mute check - error while getting current mute state'));
            } else {
                this.log.info('webOS - TV muted: %s', res.mute ? "Yes" : "No");
                callback(null, !res.mute);
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkVolumeLevel = function (callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/getVolume', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - TV volume - error while getting current volume'));
            } else {
                this.log.info('webOS - TV volume: ' + res.volume);
                callback(null, parseInt(res.volume));
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkExternalInput = function (callback) {
    if (this.connected) {
        lgtv.request('ssap://com.webos.applicationManager/getForegroundAppInfo', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - external input - error while getting external input info'));
            } else {
                this.log.info('webOS - TV current appId: %s', res.appId);
                if (res.appId.includes('com.webos.app.externalinput')) {
                    callback(null, true);
                } else {
                    callback(null, false);
                }
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkForegroundApp = function (callback) {
    if (this.connected) {
        lgtv.request('ssap://com.webos.applicationManager/getForegroundAppInfo', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - external input - error while getting external input info'));
            } else {
                this.log.info('webOS - TV current appId: %s', res.appId);
                if (res.appId === this.appSwitch) {
                    callback(null, true);
                } else {
                    callback(null, false);
                }
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkWakeOnLan = function (callback) {
    if (this.connected) {
        this.checkCount = 0;
        callback(null, true);
    } else {
        if (this.checkCount < 3) {
            this.checkCount++;
            lgtv.connect(this.url);
            setTimeout(this.checkWakeOnLan.bind(this, callback), 5000);
        } else {
            this.checkCount = 0;
            callback(new Error('webOS - wake timeout'));
        }
    }
};

webos3Accessory.prototype.getState = function (callback) {
    lgtv.connect(this.url);
    this.checkTVState.call(this, callback);
};

webos3Accessory.prototype.setState = function (state, callback) {
    if (state) {
        if (!this.connected) {
            wol.wake(this.mac, (error) => {
                if (error) return callback(new Error('webOS - wake on lan error'));
                this.checkCount = 0;
                setTimeout(this.checkWakeOnLan.bind(this, callback), 5000);
            })
        } else {
            callback(null, true);
        }
    } else {
        if (this.connected) {
            lgtv.request('ssap://system/turnOff', (err, res) => {
                if (err) return callback(null, false);
                lgtv.disconnect();
                this.connected = false;
                this.volumeService.getCharacteristic(Characteristic.On).updateValue(false);
                this.externalSourceSwitchService.getCharacteristic(Characteristic.On).updateValue(false);
                this.appSwitchService.getCharacteristic(Characteristic.On).updateValue(false);
                callback(null, true);
            })
        } else {
            callback(new Error('webOS - is not connected'))
        }
    }
};

webos3Accessory.prototype.getMuteState = function (callback) {
    setTimeout(this.checkMuteState.bind(this, callback), 50);
};

webos3Accessory.prototype.setMuteState = function (state, callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/setMute', {mute: !state});
        callback(null, state);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getVolume = function (callback) {
    setTimeout(this.checkVolumeLevel.bind(this, callback), 50);
};

webos3Accessory.prototype.setVolume = function (level, callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/setVolume', {volume: level});
        callback(null, level);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getExternalSourceSwitchState = function (callback) {
    if (!this.connected) {
        callback(null, false);
    } else {
        setTimeout(this.checkExternalInput.bind(this, callback), 50);
    }
};

webos3Accessory.prototype.setExternalSourceSwitchState = function (state, callback) {
    if (this.connected) {
        if (state) {
            lgtv.request('ssap://tv/switchInput', {inputId: this.externalSourceSwitch});
            this.setAppSwitchManuallyCallback(null, false);
        } else {
            lgtv.request('ssap://system.launcher/launch', {id: "com.webos.app.livetv"});
        }
        callback(null, state);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getAppSwitchState = function (callback) {
    if (!this.connected) {
        callback(null, false);
    } else {
        setTimeout(this.checkForegroundApp.bind(this, callback), 50);
    }
};

webos3Accessory.prototype.setAppSwitchState = function (state, callback) {
    if (this.connected) {
        if (state) {
            lgtv.request('ssap://system.launcher/launch', {id: this.appSwitch});
            this.setExternalSourceSwitchManuallyCallback(null, false);
        } else {
            lgtv.request('ssap://system.launcher/launch', {id: "com.webos.app.livetv"});
        }
        callback(null, state);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getServices = function () {
    return this.enabledServices;
};

<commit_msg>Added multiple app switches<commit_after>const lgtv2 = require('lgtv2');
const wol = require('wake_on_lan');
const tcpp = require('tcp-ping');

let Service, Characteristic;

module.exports = function (homebridge) {
    Service = homebridge.hap.Service;
    Characteristic = homebridge.hap.Characteristic;

    homebridge.registerAccessory('homebridge-webos3', 'webos3', webos3Accessory);
};

// MAIN SETUP
function webos3Accessory(log, config, api) {
    this.log = log;
    this.ip = config['ip'];
    this.name = config['name'];
    this.mac = config['mac'];
    this.keyFile = config['keyFile'];
    this.volumeControl = config['volumeControl'];
    if (this.volumeControl == undefined) {
        this.volumeControl = true;
    }
    this.pollingEnabled = config['pollingEnabled'];
    if (this.pollingEnabled == undefined) {
        this.pollingEnabled = false;
    }
    this.alivePollingInterval = config['pollingInterval'] || 5;
    this.alivePollingInterval = this.alivePollingInterval * 1000;
    this.externalSourceSwitch = config['externalSourceSwitch'];
    this.appSwitch = config['appSwitch'];

    this.url = 'ws://' + this.ip + ':3000';
    this.enabledServices = [];
    this.connected = false;
    this.checkCount = 0;
    this.checkAliveInterval = null;

    lgtv = new lgtv2({
        url: this.url,
        timeout: 5000,
        reconnect: 3000,
        keyFile: this.keyFile
    });

    lgtv.on('connect', () => {
        this.log.info('webOS - connected to TV');
        this.connected = true;
        if (!this.checkAliveInterval && this.pollingEnabled) {
            this.checkAliveInterval = setInterval(this.checkTVState.bind(this, this.pollCallback.bind(this)), this.alivePollingInterval);
        }
        lgtv.subscribe('ssap://com.webos.applicationManager/getForegroundAppInfo', (err, res) => {
            if (res && res.appId) {
                this.log.info('webOS - current appId: %s', res.appId);
            }
        });
        this.updateAccessoryStatus();
    });

    lgtv.on('close', () => {
        this.log.info('webOS - disconnected from TV');
        this.connected = false;
        //if(this.checkAliveInterval) {
        //  clearInterval(this.checkAliveInterval);
        //  this.checkAliveInterval = undefined;
        //}
    });

    lgtv.on('error', (error) => {
        this.log.error('webOS - %s', error);
        //this.connected = false;
        //setTimeout(lgtv.connect(this.url), 5000);
    });

    lgtv.on('prompt', () => {
        this.log.info('webOS - prompt for confirmation');
        this.connected = false;
    });

    lgtv.on('connecting', () => {
        this.log.debug('webOS - connecting to TV');
        this.connected = false;
    });

    this.powerService = new Service.Switch(this.name + " Power", "powerService");
    this.volumeService = new Service.Lightbulb(this.name + " Volume", "volumeService");
    this.externalSourceSwitchService = new Service.Switch(this.name + " Input: " + this.externalSourceSwitch, "externalSourceSwitchService");

    this.informationService = new Service.AccessoryInformation();

    this.powerService
        .getCharacteristic(Characteristic.On)
        .on('get', this.getState.bind(this))
        .on('set', this.setState.bind(this));

    this.volumeService
        .getCharacteristic(Characteristic.On)
        .on('get', this.getMuteState.bind(this))
        .on('set', this.setMuteState.bind(this));

    this.volumeService
        .addCharacteristic(new Characteristic.Brightness())
        .on('get', this.getVolume.bind(this))
        .on('set', this.setVolume.bind(this));

    this.externalSourceSwitchService
        .getCharacteristic(Characteristic.On)
        .on('get', this.getExternalSourceSwitchState.bind(this))
        .on('set', this.setExternalSourceSwitchState.bind(this));

    this.informationService
        .setCharacteristic(Characteristic.Manufacturer, 'LG Electronics Inc.')
        .setCharacteristic(Characteristic.Model, 'webOS TV')
        .setCharacteristic(Characteristic.SerialNumber, '-')
        .setCharacteristic(Characteristic.FirmwareRevision, '0.9.2');

    this.enabledServices.push(this.powerService);
    if (this.volumeControl) this.enabledServices.push(this.volumeService);
    if (this.externalSourceSwitch && this.externalSourceSwitch.length > 0) this.enabledServices.push(this.externalSourceSwitchService);
    this.enabledServices.push(this.informationService);

	this.prepareAppSwitchService();

}

// SETUP COMPLEX SERVICES
webos3Accessory.prototype.prepareAppSwitchService = function () {

	if(this.appSwitch == undefined ||  this.appSwitch == null || this.appSwitch.length <= 0){
		return;
	}

	let isArray = Array.isArray(this.appSwitch);

	if(isArray){
		this.appSwitchService = new Array();
		this.appSwitch.forEach((value, i) => {
			this.appSwitchService[i] = new Service.Switch(this.name + " App: " + value, "appSwitchService" + i);
		});
	}else{
		this.appSwitchService = new Service.Switch(this.name + " App: " + this.appSwitch, "appSwitchService");
	}

	if(isArray){
		this.appSwitch.forEach((value, i) => {
			this.appSwitchService[i]
				.getCharacteristic(Characteristic.On)
				.on('get', (callback) => {
						this.getAppSwitchState(callback, this.appSwitch[i]);
					})
				.on('set', (state, callback) => {
						this.setAppSwitchState(state, callback, this.appSwitch[i]);
					});
		});
	}else{
		this.appSwitchService
			.getCharacteristic(Characteristic.On)
			.on('get', (callback) => {
						this.getAppSwitchState(callback, this.appSwitch);
					})
			.on('set', (state, callback) => {
						this.setAppSwitchState(state, callback, this.appSwitch);
					});
	}

	if(isArray){
		this.appSwitch.forEach((value, i) => {
			this.enabledServices.push(this.appSwitchService[i]);
		});
	}else{
		this.enabledServices.push(this.appSwitchService);
	}

};

// HELPER METHODS
webos3Accessory.prototype.setMuteStateManuallyCallback = function (error, value) {
    if (this.volumeControl) this.volumeService.getCharacteristic(Characteristic.On).updateValue(value);
};

webos3Accessory.prototype.setExternalSourceSwitchManuallyCallback = function (error, value) {
    if (this.externalSourceSwitch && this.externalSourceSwitch.length > 0) this.externalSourceSwitchService.getCharacteristic(Characteristic.On).updateValue(value);
};

webos3Accessory.prototype.setAppSwitchManually = function (error, value, appId) {
	if(this.appSwitchService){
		if(Array.isArray(this.appSwitch)){
			if(appId == undefined || appId == null || appId.length <= 0){
				this.appSwitch.forEach((value, i) => {
					this.appSwitchService[i].getCharacteristic(Characteristic.On).updateValue(value);
				});
			}else {
				this.appSwitch.forEach((value, i) => {
					if(appId === value){
						this.appSwitchService[i].getCharacteristic(Characteristic.On).updateValue(value);
					}else {
						this.appSwitchService[i].getCharacteristic(Characteristic.On).updateValue(false);
					}
				});
			}
		}else{
			this.appSwitchService.getCharacteristic(Characteristic.On).updateValue(value);
		}
	}
};

webos3Accessory.prototype.updateAccessoryStatus = function () {
    if (this.volumeControl) this.checkMuteState(this.setMuteStateManuallyCallback.bind(this));
    if (this.externalSourceSwitch && this.externalSourceSwitch.length > 0) this.checkExternalInput(this.setExternalSourceSwitchManuallyCallback.bind(this));
	this.checkForegroundApp(this.setAppSwitchManually);
};

webos3Accessory.prototype.pollCallback = function (error, status) {
    if (!status) {
        this.powerService.getCharacteristic(Characteristic.On).updateValue(status);
        this.volumeService.getCharacteristic(Characteristic.On).updateValue(status);
    } else {
        this.powerService.getCharacteristic(Characteristic.On).updateValue(status);
    }
};

webos3Accessory.prototype.checkTVState = function (callback) {
    tcpp.probe(this.ip, 3000, (err, isAlive) => {
        if (!isAlive) {
            this.connected = false;
        } else {
            this.connected = true;
        }
        this.log.debug('webOS - TV state: %s', this.connected ? "On" : "Off");
        callback(null, this.connected);
    });
};

webos3Accessory.prototype.checkMuteState = function (callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/getStatus', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - TV mute check - error while getting current mute state'));
            } else {
                this.log.info('webOS - TV muted: %s', res.mute ? "Yes" : "No");
                callback(null, !res.mute);
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkVolumeLevel = function (callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/getVolume', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - TV volume - error while getting current volume'));
            } else {
                this.log.info('webOS - TV volume: ' + res.volume);
                callback(null, parseInt(res.volume));
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkExternalInput = function (callback) {
    if (this.connected) {
        lgtv.request('ssap://com.webos.applicationManager/getForegroundAppInfo', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - external input - error while getting external input info'));
            } else {
                this.log.debug('webOS - TV current appId: %s', res.appId);
                if (res.appId.includes('com.webos.app.externalinput')) {
                    callback(null, true);
                } else {
                    callback(null, false);
                }
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkForegroundApp = function (callback, appId) {
    if (this.connected) {
        lgtv.request('ssap://com.webos.applicationManager/getForegroundAppInfo', (err, res) => {
            if (!res || err) {
                callback(new Error('webOS - external input - error while getting external input info'));
            } else {
                this.log.info('webOS - TV current appId: %s', res.appId);
                if (res.appId === appId) {
                    callback(null, true, appId);
                } else {
                    callback(null, false, appId);
                }
            }
        });
    } else {
        callback(null, false);
    }
};

webos3Accessory.prototype.checkWakeOnLan = function (callback) {
    if (this.connected) {
        this.checkCount = 0;
        callback(null, true);
    } else {
        if (this.checkCount < 3) {
            this.checkCount++;
            lgtv.connect(this.url);
            setTimeout(this.checkWakeOnLan.bind(this, callback), 5000);
        } else {
            this.checkCount = 0;
            callback(new Error('webOS - wake timeout'));
        }
    }
};

// HOMEBRIDGE STATE SETTERS/GETTERS
webos3Accessory.prototype.getState = function (callback) {
    lgtv.connect(this.url);
    this.checkTVState.call(this, callback);
};

webos3Accessory.prototype.setState = function (state, callback) {
    if (state) {
        if (!this.connected) {
            wol.wake(this.mac, (error) => {
                if (error) return callback(new Error('webOS - wake on lan error'));
                this.checkCount = 0;
                setTimeout(this.checkWakeOnLan.bind(this, callback), 5000);
            })
        } else {
            callback(null, true);
        }
    } else {
        if (this.connected) {
            lgtv.request('ssap://system/turnOff', (err, res) => {
                if (err) return callback(null, false);
                lgtv.disconnect();
                this.connected = false;
                this.volumeService.getCharacteristic(Characteristic.On).updateValue(false);
                this.externalSourceSwitchService.getCharacteristic(Characteristic.On).updateValue(false);
				this.setAppSwitchManually(null, false, null);
                callback(null, true);
            })
        } else {
            callback(new Error('webOS - is not connected'))
        }
    }
};

webos3Accessory.prototype.getMuteState = function (callback) {
    setTimeout(this.checkMuteState.bind(this, callback), 50);
};

webos3Accessory.prototype.setMuteState = function (state, callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/setMute', {mute: !state});
        callback(null, state);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getVolume = function (callback) {
    setTimeout(this.checkVolumeLevel.bind(this, callback), 50);
};

webos3Accessory.prototype.setVolume = function (level, callback) {
    if (this.connected) {
        lgtv.request('ssap://audio/setVolume', {volume: level});
        callback(null, level);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getExternalSourceSwitchState = function (callback) {
    if (!this.connected) {
        callback(null, false);
    } else {
        setTimeout(this.checkExternalInput.bind(this, callback), 50);
    }
};

webos3Accessory.prototype.setExternalSourceSwitchState = function (state, callback) {
    if (this.connected) {
        if (state) {
            lgtv.request('ssap://tv/switchInput', {inputId: this.externalSourceSwitch});
            this.setAppSwitchManually(null, false, null);
        } else {
            lgtv.request('ssap://system.launcher/launch', {id: "com.webos.app.livetv"});
        }
        callback(null, state);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getAppSwitchState = function (callback, appId) {
    if (!this.connected) {
        callback(null, false);
    } else {
        setTimeout(this.checkForegroundApp.bind(this, callback, appId), 50);
    }
};

webos3Accessory.prototype.setAppSwitchState = function (state, callback, appId) {
    if (this.connected) {
        if (state) {
            lgtv.request('ssap://system.launcher/launch', {id: appId});
            this.setExternalSourceSwitchManuallyCallback(null, false);
			this.setAppSwitchManually(null, true, appId);
        } else {
            lgtv.request('ssap://system.launcher/launch', {id: "com.webos.app.livetv"});
        }
        callback(null, state);
    } else {
        callback(new Error('webOS - is not connected'))
    }
};

webos3Accessory.prototype.getServices = function () {
    return this.enabledServices;
};

<|endoftext|>


<commit_before>// Dependencies
var ArtStack = require("artstack")
  , Request = require("request")
  , CliUpdate = require("cli-update")
  , CliBox = require("cli-box")
  , Config = require("./config")
  , Fs = require("fs")
  , Path = require("path")
  ;

const DOWNLOAD_DIR = Path.resolve(__dirname + "/downloads/");

ArtStack.auth(Config, function (err) {
    if (err) throw err;
    ArtStack.tags.list(function (err, artists) {
        if (err) throw err;
        console.log("Fetched " + artists.length + " artists.");

        var downloads = {}
          , cUsr = null
          , ii = 0
          , url = null
          , path = null
          , userDir = null
          , k = null
          , c = null
          ;

        function updateScreen() {

            var content = "";
            // x .... 100
            // p .... c
            for (k in downloads) {
                c = downloads[k];
                content += k + ": " + c.progress + "/" + c.count + "\n"
            }

            var box = new CliBox({
                fullscreen: true
              , marks: {}
            }, {
                text: content
              , hAlign: "left"
              , vAlign: "top"
            });

            CliUpdate.render(box.toString());
        }

        function handleUsr(user) {
            userDir = DOWNLOAD_DIR + "/" + user.display_name + "/";
            try {
                Fs.mkdirSync(userDir)
            } catch (e) {}

            ArtStack.artworks.fromArtist(user, function (err, artws) {
                if (err) throw err;

                cUsr = downloads[user.display_name] = {
                    progress: 0
                  , count: artws.length
                };

                for (ii = 0; ii < artws.length; ++ii) {
                    url = artws[ii].url;
                    path = userDir + url.match(/\/([0-9]+)\//)[1] + "." + url.match(/\/.*\.(.*)\?.*$/)[1];
                    if (!Fs.existsSync(path)) {
                        Request(url, function (err, res, body) {
                            ++cUsr.progress;
                            updateScreen();
                        }).pipe(Fs.createWriteStream(path))
                    } else {
                        ++cUsr.progress;
                        updateScreen();
                    }
                }
            });
        }

        for (var i = 0; i < artists.length; ++i) {
            handleUsr(artists[i]);
        }
    });
});
<commit_msg>Added conf<commit_after>// Dependencies
var ArtStack = require("artstack")
  , Request = require("request")
  , CliUpdate = require("cli-update")
  , CliBox = require("cli-box")
  , Config = require("./conf")
  , Fs = require("fs")
  , Path = require("path")
  ;

const DOWNLOAD_DIR = Path.resolve(__dirname + "/downloads/");

ArtStack.auth(Config, function (err) {
    if (err) throw err;
    ArtStack.tags.list(function (err, artists) {
        if (err) throw err;
        console.log("Fetched " + artists.length + " artists.");

        var downloads = {}
          , cUsr = null
          , ii = 0
          , url = null
          , path = null
          , userDir = null
          , k = null
          , c = null
          ;

        function updateScreen() {

            var content = "";
            // x .... 100
            // p .... c
            for (k in downloads) {
                c = downloads[k];
                content += k + ": " + c.progress + "/" + c.count + "\n"
            }

            var box = new CliBox({
                fullscreen: true
              , marks: {}
            }, {
                text: content
              , hAlign: "left"
              , vAlign: "top"
            });

            CliUpdate.render(box.toString());
        }

        function handleUsr(user) {
            userDir = DOWNLOAD_DIR + "/" + user.display_name + "/";
            try {
                Fs.mkdirSync(userDir)
            } catch (e) {}

            ArtStack.artworks.fromArtist(user, function (err, artws) {
                if (err) throw err;

                cUsr = downloads[user.display_name] = {
                    progress: 0
                  , count: artws.length
                };

                for (ii = 0; ii < artws.length; ++ii) {
                    url = artws[ii].url;
                    path = userDir + url.match(/\/([0-9]+)\//)[1] + "." + url.match(/\/.*\.(.*)\?.*$/)[1];
                    if (!Fs.existsSync(path)) {
                        Request(url, function (err, res, body) {
                            ++cUsr.progress;
                            updateScreen();
                        }).pipe(Fs.createWriteStream(path))
                    } else {
                        ++cUsr.progress;
                        updateScreen();
                    }
                }
            });
        }

        for (var i = 0; i < artists.length; ++i) {
            handleUsr(artists[i]);
        }
    });
});
<|endoftext|>